此repo演示了注册InstancePerTenant作用域依赖关系TestMultitenancyContext,该关系在Home Controller中解析.由于使用IHttpContextAccessor的问题,我使用自定义RequestMiddleware类来捕获当前的HttpContext对象,以便我可以在MultitenantIdentificationStrategy中的当前HttpContext请求对象上执行逻辑.
最后,TestFixture提供了一个简单的xUnit测试,至少在我的机器上为两个租户返回“tenant1”.
有没有我在这里错过的或者这只是目前没有工作?
更新10/6/2017: We released Autofac.AspNetCore.Multitenant以更易于使用的软件包结束解决方案.我会在这里留下原始答案/解释给后人,但是如果你打这个,你可以抓住那个包然后继续前进.我认为你遇到了时间问题.
如果在中间件中弹出HttpContext上的调试器,则可以看到在名为ServiceProvidersFeature的属性上有一个RequestServicesFeature对象. That’s what’s responsible for creating the per-request scope.范围在第一次访问时创建.
似乎订单大致如下:
> WebHostBuilder
adds a startup filter to enable request services to be added to the pipeline.
>启动过滤器AutoRequestServicesStartupFilter
将中间件添加到管道的最开头,以触发请求服务的创建.
>添加的中间件RequestServicesContainerMiddleware
基本上只是从ServiceProvidersFeature调用RequestServices属性来触发创建每个请求的生命周期范围.但是,它用于创建请求范围的in its constructor is where it gets the IServiceScopeFactory
,这不是很好,因为它将在可以建立租户之前从根容器创建.
所有这些都会导致已经确定每个请求范围是针对默认租户的情况,并且您无法真正更改它.
要解决此问题,您需要自己设置请求服务,以便它们考虑多租户.
听起来比实际情况更糟糕.
首先,我们需要对应用程序容器的引用.我们需要能够从应用程序级服务而不是请求服务来解析某些东西.我通过向您的Startup类添加静态属性并将容器保留在那里来做到这一点.
public static IContainer ApplicationContainer { get; private set; }
接下来,我们将更改您的中间件看起来更像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; } } } }
现在,您需要一个启动过滤器来获取中间件.您需要一个启动过滤器,否则RequestServicesContainerMiddleware将在管道中运行得太早,并且已经开始从错误的租户范围开始解析.
public class RequestStartupFilter : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return builder => { builder.UseMiddleware<RequestMiddleware>(); next(builder); }; } }
将启动筛选器添加到服务集合的最开头.您需要在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); }
请注意插入呼叫,以便在启动过滤器之前将服务注册置于顶部.
新的运营顺序将是:
>在app启动时……
>您的启动过滤器会将您的自定义请求服务中间件添加到管道中.
> AutoRequestServicesStartupFilter将RequestServicesContainerMiddleware添加到管道.
>在请求期间……
>您的自定义请求中间件将根据入站请求信息设置请求服务.
> RequestServicesContainerMiddleware将看到已经设置了请求服务,并且什么都不做.
>解析服务后,请求服务范围已经是您的自定义请求中间件设置的租户范围,并且将显示正确的内容.
我通过将租户ID切换为来自查询字符串而不是主机名来本地测试这个(因此我没有设置主机文件条目和所有爵士乐)并且我能够通过切换查询字符串参数来切换租户.
现在,您可以稍微简化一下.例如,通过直接对Program类中的Web主机构建器执行某些操作,您可以在没有启动过滤器的情况下逃脱.您可以在调用builder.Populate并跳过该Insert调用之前,使用ContainerBuilder注册您的启动过滤器.如果您不希望Autofac在系统中传播,则可以将IServiceProvider存储在Startup类属性中.如果您创建中间件实例并自己将容器作为构造函数参数传递,则可以在没有静态容器属性的情况下离开.不幸的是,我已经花了很多时间试图找出解决方法所以我将不得不将“优化它”作为读者的练习.
再次,对不起,这不清楚. I’ve filed an issue on your behalf to get the docs updated and maybe figure out a better way to do this that’s a little more straightforward.