asp.net 核心 3.1 将 autofac 与 finbuckle 集成

asp.net core 3.1 integrate autofac with finbuckle

我正在构建一个多租户 asp.net 核心 3.1 应用程序,其中包含多个数据库(每个租户一个,一个主数据库)。我正在使用 Autofac 为我的服务提供每个租户生命周期的 Singleton 支持(现在我不需要租户覆盖,我只需要自定义 SinglePerTenant 生命周期)我正在使用

我的租户识别策略涉及对主数据库的数据库调用。

正如 autofac 文档中所写,识别策略不应调用数据库,因为它在每个依赖项解析时都会被调用。然后我使用了另一种解决方案来识别租户 (Finbuckle.MultiTenant)。

对于 Finbukle,当请求到达时调用他的识别策略(每个 htp 请求一次),我将 db 调用放在他的识别策略中(为了优化我可以缓存结果,并每天刷新一次查询)并且在 HttpContext 中设置了一个 tenantInfo 对象。

然后在 AutoFac 识别策略中,我尝试读取由 FinBuckle 设置的对象,但这是不可能的,因为 Autofac 识别策略在 FinBuckle 之前被调用,并且所需的 属性 为空。

我的Program.cs是:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Startup.cs :

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMultiTenant().WithStrategy<TestStategy>(ServiceLifetime.Singleton).WithStore<CustomTestStore>(ServiceLifetime.Singleton); //enable the multitenant support from finbukle

        services.AddControllers();

        services.AddAutofacMultitenantRequestServices();

    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseMultiTenant() //finbukle

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

    public void ConfigureContainer(ContainerBuilder builder)
    {
         builder.RegisterType<TestDiA>().As<ITestDI>().InstancePerTenant();
    }

    public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
    {
        // This is the MULTITENANT PART. Set up your tenant-specific stuff here.
        var strategy = new MyAutofacTenantIdentificationStrategy(container.Resolve<IHttpContextAccessor>());
        var mtc = new MultitenantContainer(strategy, container);
        return mtc;
    }
}

Autofac租户识别策略:

public class MyAutofacTenantIdentificationStrategy : ITenantIdentificationStrategy
{
    private readonly IHttpContextAccessor httpContextAccessor;
    public MyAutofacTenantIdentificationStrategy(
      IHttpContextAccessor httpContextAccessor
    )
    {
        this.httpContextAccessor = httpContextAccessor;
    }
    public bool TryIdentifyTenant(out object tenantId)
    {
        tenantId = null;
        var context = httpContextAccessor.HttpContext;
        if (context == null)
            return false;


        var identifier = context.getTenatInfo()?.Identifier ?? null; //getTenantInfo is a method that extract the tenant info object setted by finbukle
        tenantId = identifier;
        return (tenantId != null || tenantId == (object)"");
    }
}

我正在使用 Autofac.AspNetCore.Multitenant 3.0.0、Autofac.Extensions.DependencyInjection 6.0.0 和 FinBuckle.MultiTenant 5.0.4

我在这方面真的很新,所以如果我问了一个微不足道的问题,我深表歉意。 有没有办法用这种方法解决问题?

或者我的问题有替代策略?

目前我认为 Finbuckle 和 Autofac.Multitenant 不兼容。

Autofac 多租户支持 ASP.NET 核心 relies on running first thing in the middleware pipeline so it can set HttpContext.RequestServices to be based on the tenant scope。当然,作为其中的一部分,租户识别策略将 运行.

但是,Finbuckle 假定每个租户都共享一个容器,就像默认的ASP.NET 核心功能一样。 Finbuckle 中间件 tries to use HttpContext.RequestServices to identify the tenant based on registered strategies.

您可以看到这引发的那种 chicken/egg 问题 - 请求服务应基于租户生命周期范围,但租户识别策略需要从请求服务中解决问题。

但是,让我们暂时忽略它,因为问题是如何避免在每次解析时都调用数据库进行标识。

如果您深入研究 Finbuckle 代码,中间件 sets the tenant information on HttpContext.Items as part of their middleware running. Later, when you retrieve the tenant info, it gets retrieved from HttpContext.Items, not re-resolved through the database。数据库调用只执行一次,第一次租户ID是运行.

这可能没问题。根据您计划支持的租户数量以及他们更改的频率,添加某种内存缓存层可能是值得的,您可以使用它来存储租户 ID 数据(无论存储在数据库中以帮助您识别该租户的任何内容)所以你可以在访问数据库之前先尝试内存存储。也许它会定期使那里的数据过期,或者它可能是固定大小或其他什么……缓存策略完全取决于应用程序,我无法就此推荐具体细节。重点是,这是减轻数据库调用的一种方法。

但回到 chicken/egg 问题,我没有找到解决该问题的简单方法。

如果是我,我必须让这个工作,我可能 skip calling the IApplicationBuilder.UseMultiTenant() extension and then create my own version of their middleware which, instead of using HttpContext.RequestServices to get the tenant ID strategies, would take a multitenant container right in the constructor like the Autofac multitenant middleware and would directly use the application-level container to resolve those strategies. That would, of course, have to run before the Autofac multitenant request services middleware, and forcing middleware order is sort of painful。最后,由于 HttpContext.Items 最终会在中间件 运行 之后获得租户标识,因此您的 Autofac ITenantIdentificationStrategy 可以简单地查看那里以获取数据,而根本不调用数据库。

然而...

非常重要的免责声明

  • 我一生中从未使用过 Finbuckle。我可以浏览 GitHub 上的代码,但我不知道上述考虑可能会产生什么副作用。
  • 上面的考虑我没有实际尝试过。它可能不起作用。
  • 我是 Autofac 项目维护者并编写了原始的多租户支持,包括与 ASP.NET 核心的原始集成。让它工作 非常 痛苦...所以当我说上述考虑可能很棘手时,我已经在那里了。
  • 我非常明确地将其称为 'consideration' - 需要考虑的事情 - 不是建议 因为我不一定要 'recommend' 我想做的事情没有太多的信心。我没有信心,因为,再一次,我不使用 Finbuckle。

我的实际建议 是......好吧,放慢一点。正如您提到的,您是这个领域的新手,看起来您将 运行 进入这里的内容非常深入。如果您还没有,我建议您深入研究 the actual Finbuckle code on GitHub。它看起来并不多,它可以让您了解正在发生的事情。我建议尝试使用 just Autofac 多租户和 just Finbuckle 创建一个多租户应用程序。看看你是否真的需要两者。也许只有一个是有道理的。例如,Finbuckle 似乎已经具备数据存储的多租户功能;这也是许多人使用 Autofac 多租户的目的——为每个租户注册不同的数据库上下文。也许只使用其中一种产品就足够了,而且可以解决整个问题。

您可以查看我的解决方案。我有一个类似的租户解析实现,比如 Finbuckle。我在 Autofac TryIdentifyTenant 中注入了解析策略,并使用它来解析每个请求的 tenantId。

public MultiTenantResolverStrategy(IHttpContextAccessor httpContext, ITenantResolverStrategy strategy)
        {
            _httpContext = httpContext;
            _strategy = strategy;
        }

        /// <summary>
        /// Get Tenant identifier based on the Tenant Resolver Strategy
        /// </summary>
        /// <param name="tenantId"></param>
        /// <returns></returns>
        public bool TryIdentifyTenant(out object tenantId)
        {
            tenantId = null;
            if (_httpContext.HttpContext != null)
            {
                tenantId = _strategy.GetTenantIdentifierAsync(_httpContext.HttpContext).GetAwaiter().GetResult();
            }

            
            return tenantId!=null;
        }

您可以在 https://github.com/AHacker02/multi-tenant-webapp 查看完整代码 如果您认为我所做的任何事情是不好的做法,请告诉我。