在 ConfigureMultitenantContainer 中解决依赖关系

Resolving dependencies in the ConfigureMultitenantContainer

我正在尝试解决 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 中注册的其他依赖项。我的实现有什么问题吗?

有些事情可能会给您带来麻烦。

首先,我看到你的租户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<string, object>来缓存主机名到租户 ID 的映射,这就是很好(只要你锁定它,或者使用线程安全的字典)。但是您有一个 单个对象 可以跨线程覆盖,这是个坏消息。

接下来,我看到您的租户 ID 策略需要依赖项。 通常,我会避免这种情况并直接构建它。我知道这对某些人来说不是很好,但是有一种倾向 "over-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;
}

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

所以,全部煮沸:

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