aspof核心应用程序中的Autofac.Multitenant似乎无法正确解析租户范围的依赖关系

Sal*_*lty 4 autofac asp.net-core

我正在升级使用Autofac.Multitenant框架的Multitenant dotnet核心解决方案.我没有很多运气让租约解决方案正常工作.我在这里创建了一个简单的问题演示:https://github.com/SaltyDH/AutofacMultitenancy1

此repo演示了如何注册在Home Controller中解析的InstancePerTenant作用域依赖项TestMultitenancyContext.由于使用IHttpContextAccessor的问题,我使用自定义RequestMiddleware类来捕获当前的HttpContext对象,以便我可以在当前的HttpContext请求对象上执行逻辑MultitenantIdentificationStrategy.

最后,TestFixture提供一个简单的xUnit测试,至少在我的机器上为两个租户返回"tenant1".

有没有我在这里错过的或者这只是目前没有工作?

Tra*_*lig 9

更新10/6/2017:我们发布了Autofac.AspNetCore.Multitenant,以便在更易于使用的软件包中完成解决方案.我会在这里留下原始答案/解释给后人,但是如果你打这个,你可以抓住那个包然后继续前进.


我认为你遇到了时间问题.

如果你在中间件的HttpContext上弹出调试器,你会发现RequestServicesFeature在一个名为的属性上有一个对象ServiceProvidersFeature.这就是创建每个请求范围的责任.范围在第一次访问时创建.

似乎订单大致如下:

  1. WebHostBuilder添加了启动过滤器,使请求的服务被添加到管道.
  2. 启动过滤器AutoRequestServicesStartupFilter将中间件添加到管道的最开头,以触发请求服务的创建.
  3. 添加的中间件RequestServicesContainerMiddleware,基本上只是调用RequestServices属性ServiceProvidersFeature来触发创建每个请求的生命周期范围.但是,在它的构造函数中,它IServiceScopeFactory用于创建请求范围,它不是那么好,因为它将在根据容器创建之前创建租户.

所有这些都会导致已经确定每个请求范围是针对默认租户的情况,并且您无法真正更改它.

要解决此问题,您需要自己设置请求服务,以便它们考虑多租户.

听起来比实际情况更糟糕.

首先,我们需要对应用程序容器的引用.我们需要能够从应用程序级服务而不是请求服务来解析某些东西.我通过向static您的Startup类添加属性并将容器保留在那里来做到这一点.

public static IContainer ApplicationContainer { get; private set; }
Run Code Online (Sandbox Code Playgroud)

接下来,我们将更改您的中间件看起来更像是RequestServicesContainerMiddleware.您需要设置第HttpContext一个,以便您的租户ID策略有效.之后,您可以获得IServiceScopeFactory并遵循他们所做的相同模式RequestServicesContainerMiddleware.

public class RequestMiddleware
{
  private static readonly AsyncLocal<HttpContext> _context = new AsyncLocal<HttpContext>();

  private readonly RequestDelegate _next;

  public RequestMiddleware(RequestDelegate next)
  {
    this._next = next;
  }

  public static HttpContext Context => _context.Value;

  public async Task Invoke(HttpContext context)
  {
    _context.Value = context;
    var existingFeature = context.Features.Get<IServiceProvidersFeature>();
    using (var feature = new RequestServicesFeature(Startup.ApplicationContainer.Resolve<IServiceScopeFactory>()))
    {
      try
      {
        context.Features.Set<IServiceProvidersFeature>(feature);
        await this._next.Invoke(context);
      }
      finally
      {
        context.Features.Set(existingFeature);
        _context.Value = null;
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要一个启动过滤器来获取中间件.您需要一个启动过滤器,否则RequestServicesContainerMiddleware将在管道中运行太早,并且事情已经开始从错误的租户范围解决.

public class RequestStartupFilter : IStartupFilter
{
  public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
  {
    return builder =>
    {
      builder.UseMiddleware<RequestMiddleware>();
      next(builder);
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

将启动筛选器添加到服务集合的最开头.您需要先运行启动过滤器AutoRequestServicesStartupFilter.

ConfigureServices最终看起来是这样的:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
  services.Insert(0, new ServiceDescriptor(typeof(IStartupFilter), typeof(RequestStartupFilter), ServiceLifetime.Transient));
  services.AddMvc();

  var builder = new ContainerBuilder();
  builder.RegisterType<TestMultitenancyContext>().InstancePerTenant();

  builder.Populate(services);
  var container = new MultitenantContainer(new MultitenantIdentificationStrategy(), builder.Build());
  ApplicationContainer = container;
  return new AutofacServiceProvider(container);
}
Run Code Online (Sandbox Code Playgroud)

请注意其中的Insert呼叫在启动过滤器之前将服务注册置于顶部.

新的运营顺序将是:

  1. 在app启动时......
    1. 您的启动过滤器会将您的自定义请求服务中间件添加到管道中.
    2. AutoRequestServicesStartupFilter将添加RequestServicesContainerMiddleware到管道.
  2. 在请求期间......
    1. 您的自定义请求中间件将根据入站请求信息设置请求服务.
    2. RequestServicesContainerMiddleware会看到,请求服务已经建立,并会做什么.
    3. 解析服务后,请求服务范围已经是您的自定义请求中间件设置的租户范围,并且将显示正确的内容.

我通过将租户ID切换为来自querystring而不是主机名来本地测试这个(因此我没有设置主机文件条目和所有爵士乐)并且我能够通过切换查询字符串参数来切换租户.

现在,您可以稍微简化一下.例如,通过直接对类中的Web主机构建器执行某些操作,您可以在没有启动过滤器的情况下离开Program.您可以使用ContainerBuilder之前的呼叫注册您的启动过滤器builder.Populate并跳过该Insert呼叫.如果您不希望Autofac在系统中传播,您可以将其存储IServiceProviderStartupclass属性中.如果您创建中间件实例并自己将容器作为构造函数参数传递,则可以在没有静态容器属性的情况下离开.不幸的是,我已经花了很多时间试图找出解决方法所以我将不得不将"优化它"作为读者的练习.

再次,对不起,这不清楚.我已经代表您提交了一个问题,以便更新文档,并且可能找出更好的方法来实现这一点,这更加简单明了.