【问题标题】:Resolving dependencies in the ConfigureMultitenantContainer在 ConfigureMultitenantContainer 中解决依赖关系
【发布时间】:2019-10-07 13:37:30
【问题描述】:

我正在尝试在ConfigureMultitenantContainer 中解析ITenantIdentificationStrategy,但我有An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll

我已经在ConfigureContainer注册了TenantResolverStrategy

public void ConfigureContainer(ContainerBuilder builder)
{
  builder.RegisterType<TenantResolverStrategy>().As<ITenantIdentificationStrategy>();
}

我想解析ConfigureMultitenantContainer中的ITenantIdentificationStrategy

public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
  var strategy = container.Resolve<ITenantIdentificationStrategy>();
  var mtc = new MultitenantContainer(strategy, container);
  // mtc.ConfigureTenant("a", cb => cb.RegisterType<TenantACustom>().As<ITenantCustom>());
  return mtc;
}

但是它正在抛出An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll

我的ITenantIdentificationStrategy是这样实现的:

public class TenantResolverStrategy : ITenantIdentificationStrategy
{
  public TenantResolverStrategy(
    IHttpContextAccessor httpContextAccessor,
    IMemoryCache memoryCache,
    TenantEntity tenantEntity
  )
  {
    this.httpContextAccessor = httpContextAccessor;
    this.memoryCache = memoryCache;
    this.tenantEntity = tenantEntity;
  }

  public bool TryIdentifyTenant(out object tenantId)
  {
    tenantId = null;

    var context = httpContextAccessor.HttpContext;
    var hostName = context?.Request?.Host.Value;

    tenantEntity = GetTenant(hostName);
    if (tenantEntity != null)
    {
      tenantId = tenantEntity.TenantCode;
    }

    return (tenantId != null || tenantId == (object)"");
  }
}

我在Program.cs中注册ConfigureMultitenantContainer如下:

var host = Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))

我也无法解决我在ConfigureContainer 中注册的其他依赖项。我的实现有什么问题吗?

【问题讨论】:

  • 我怀疑您的某个对象无法解析,这就是问题所在。你能让非多租户容器工作并解决你需要的对象吗?
  • 我认为你是对的。我无法解析 DbContext,但我能够解析与 DbContext 无关的其他依赖项。
  • 经过进一步检查,如果ITenantIdentificationStrategy没有与DbContext相关的依赖,则DbContext可以在其​​他类中解析。由于我需要根据存储在 DbContext 中的主机名来识别租户,我该如何以其他方式做到这一点?
  • 看错误信息,其实是无法解析DbContextOption。 An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll: 'An exception was thrown while activating WebApi.Saas.TenantResolverStrategy2 -&gt; Data.System.Databases.SystemDbContext -&gt; λ:Microsoft.EntityFrameworkCore.DbContextOptions[[Data.System.Databases.SystemDbContext, Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]。但是我该如何解决呢?

标签: c# dependency-injection autofac ioc-container asp.net-core-3.0


【解决方案1】:

这里有几件事可能会给您带来麻烦。

首先,我看到您的租户 ID 策略不是单例

builder.RegisterType<TenantResolverStrategy>().As<ITenantIdentificationStrategy>();

这很麻烦,因为租户 ID 策略的每个解决方案都将通过租户 ID 策略的单个实例。它将被缓存。但是,解决策略会解决不同的价值观并产生误导。

考虑:

var strategy = container.Resolve<ITenantIdentificationStrategy>();
// The multitenant container is CACHING THIS.
var mtc = new MultitenantContainer(container, strategy);
// Now, later on you maybe resolve another instance of the strategy:
var anotherInstance = mtc.Resolve<ITenantIdentificationStrategy>();
// Or from the root:
var thirdInstance = container.Resolve<ITenantIdentificationStrategy>();

// OH NO! strategy != anotherInstance != thirdInstance
// These ARE NOT THE SAME INSTANCE. Tenant determination may CHANGE
// based on which one of these is used.

将您的租户 ID 策略设为单例。

接下来,由于策略缓存在多租户容器中,您无法维护状态。这非常重要,因为您会遇到大量线程问题。

public class TenantResolverStrategy : ITenantIdentificationStrategy
{
  public TenantResolverStrategy(
    IHttpContextAccessor httpContextAccessor,
    IMemoryCache memoryCache,
    TenantEntity tenantEntity
  )
  {
    this.httpContextAccessor = httpContextAccessor;
    this.memoryCache = memoryCache;

    // PROBLEM! Where is TenantEntity coming from?
    this.tenantEntity = tenantEntity;
  }

  public bool TryIdentifyTenant(out object tenantId)
  {
    tenantId = null;

    var context = httpContextAccessor.HttpContext;
    var hostName = context?.Request?.Host.Value;

    // PROBLEM: Incorrectly storing state in the strategy
    // when this is used across threads. (There's also no
    // explanation of what's in GetTenant, so it's hard to
    // help with that.) 
    tenantEntity = GetTenant(hostName);
    if (tenantEntity != null)
    {
      tenantId = tenantEntity.TenantCode;
    }

    return (tenantId != null || tenantId == (object)"");
  }
}

您可以维护缓存,但不维护状态。例如,您可能需要 Dictionary&lt;string, object&gt; 来缓存主机名到租户 ID 的映射,这很好(只要您这样做锁定它,或使用线程安全字典)。但是您有一个单个对象,可以跨线程覆盖,这是个坏消息。

接下来,我看到您的租户 ID 策略需要依赖项。 一般来说,我会避免这种情况并直接构建它。我知道这对某些人来说不是很好,但是有一种趋势是“过度 DI”不应该涉及 DI 的事情。手动构造基础对象,如 ContainerBuilder 或租户 ID 策略,可确保您只查看您可以控制的内容(并且避免了这些异常情况,就像您看到的那样)。

您应该能够手动解决租户 ID 策略中的任何依赖项。 例如,这应该可以:

public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
  // These are the dependencies of the strategy. You don't NEED TO DO THIS
  // but if you put these in here, it SHOULD NOT BLOW UP. If it does, you know
  // where to start tracing things down.
  var accessor = container.Resolve<IHttpContextAccessor>();
  var cache = container.Resolve<IMemoryCache>();
  var entity = container.Resolve<TenantEntity>();

  // Here's the strategy - again, make sure it's a SINGLETON!
  var strategy = container.Resolve<ITenantIdentificationStrategy>();
  var mtc = new MultitenantContainer(strategy, container);
  // mtc.ConfigureTenant("a", cb => cb.RegisterType<TenantACustom>().As<ITenantCustom>());
  return mtc;
}

也就是说,我认识到可能需要注入诸如数据库连接之类的东西,在这种情况下,再次确保将这些东西标记为 singletons。您的多租户容器租户 ID 策略将在应用程序的整个生命周期内有效。另外,任何依赖于租户 ID 策略(如多租户容器)的内容都不应是特定于租户或基于请求的,因为......如果没有有效的租户 ID 策略,您将无法确定租户。循环依赖!

所以,把这一切都归结为:

  • 将您的租户 ID 策略注册为单例。
  • 删除租户 ID 策略中所有非单例的依赖项(例如,TenantEntity)。
  • 对主机到租户 ID 的映射使用线程安全缓存(例如内存缓存),但不要存储状态(不要保留 TenantEntity 实例变量;使其成为方法级别的局部变量如果需要)。
  • 确保您需要解决的所有问题都已注册。如果您的租户 ID 策略需要 IHttpContextAccessor(或 IMemoryCache,或其他),则需要注册。如果遇到麻烦,请尝试直接解决这些依赖关系;这将准确地告诉您哪个组件有问题。 (但是,如果您查看收到的异常的完整堆栈跟踪,您应该确切地看到发生了什么。您没有在问题中包含该异常消息,因此我们无法深入研究。)李>

【讨论】:

  • 我无法解析任何与 DbContextOption 相关的类。除此以外是可以解决的。我需要根据主机名获取租户信息,包括连接字符串。我收到的异常消息是An unhandled exception of type 'Autofac.Core.DependencyResolutionException' occurred in Autofac.dll: 'An exception was thrown while activating WebApi.Saas.TenantResolverStrategy2 -&gt; Data.System.Databases.SystemDbContext -&gt; λ:Microsoft.EntityFrameworkCore.DbContextOptions[[Data.System.Databases.SystemDbContext, Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
  • 您注册了“DbContextOption”吗?您需要注册您想要解决的所有问题
  • 我能够在 ConfigureMultitenantContainer 之外/之后解析 DbContext 和 DbContextOption。
  • 这里没有足够的帮助。我们无法从您的代码中看到 DbContextOption 注册的位置。不过没关系,归结为:如果在解析策略时无法从容器中解析它,则说明它没有正确注册。也许它没有尽快注册,也许它取决于某种多租户支持,也许它被错误地注册为每个请求的实例,可能是很多不同的事情。这就是您必须解决的问题,因为这里没有足够的人可以提供帮助。祝你好运!
  • 我已经通过简化 ITenantIdentificationStrategy 并删除 DbContext 相关类来解决它。并使租户可在单独的容器寄存器上注入。感谢您强调那些可能是潜在问题的要点。
猜你喜欢
  • 1970-01-01
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 2017-02-20
  • 1970-01-01
相关资源
最近更新 更多