Hangfire - 多租户,ASP.NET Core - 解决正确的租户

14
  • 已经有一个需要使用 Hangfire 的 SaaS 项目,我们已经实现了识别租户的要求。
  • 架构
    • 持久层
      • 每个租户都有自己的数据库
    • .NET Core
      • 我们已经有一个名为 TenantCurrentService 的服务,它从源列表 [主机名、查询字符串等] 返回租户的 ID
      • 我们已经有一个用于 Entity Framework 的 DbContextFactory,它会返回一个带有正确连接字符串的 DB 上下文,以供客户端使用
      • 我们目前正在使用 ASP.NET Core DI(如果有帮助,愿意进行更改)
    • Hangfire
      • 无论租户数量如何,都使用单一存储(例如:Postgresql)
      • 在适当的容器/ServiceCollection 中执行作业,以便我们检索到正确的数据库、正确的设置等。
  • 问题
  • 我试图将 TenantId 添加到从TenantCurrentService检索到的作业中(它是一个 Scoped 服务)。

    然后当作业被执行时,我们需要从作业中检索 TenantId 并将其存储在 HangfireContext 中,以便 TenantCurrentService 知道从 Hangfire 检索到的 TenantId。然后,我们的应用程序层将能够从我们的 DbContextFactory 连接到正确的数据库。

  • 当前状态
    • 目前,我们已经能够使用 IClientFilter 存储从我们的服务中检索到的 tenantId。
    • 我如何从 IServerFilter 中检索我的当前 ASP.NET Core DI ServiceScope(负责检索保存的作业参数),以便我可以调用 .GetRequiredService().IdentifyTenant(tenantId)

    是否有关于此问题的好文章/或任何提示可以提供?


    听起来你已经在Hangfire和ASP.NET Core DI方面做了很多工作。你能给我指一些描述复杂部分的文章吗?目前我遇到的问题是,在适当的容器/ServiceCollection中执行作业,以便在处理作业时与处理Web请求时获取不同的依赖项。https://stackoverflow.com/questions/66618098/hangfire-dependency-injection-with-net-core-inject-different-objects-when-proc - Rory
    1个回答

    13

    首先,您需要能够在TenantCurrentService中设置TenantId。 然后,您可以依靠过滤器:

    客户端(您在其中排队作业)

    public class ClientTenantFilter : IClientFilter
    {
            public void OnCreating(CreatingContext filterContext)
            {
               if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
    
                filterContext.SetJobParameter("TenantId", TenantCurrentService.TenantId);
            }
    }
    

    并且是在服务器端(作业被出队的地方)。

    public class ServerTenantFilter : IServerFilter
    {
        public void OnPerforming(PerformingContext filterContext)
        {
          if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
    
          var tenantId = filterContext.GetJobParameter<string>("TenantId");
          TenantCurrentService.TenantId = tenantId;
        }
    }
    

    当您通过IJobFilterProvider配置服务器时,可以声明服务器过滤器:

            var options = new BackgroundJobServerOptions
            {
                Queues = ...,
                FilterProvider = new ServerFilterProvider()
            };
            app.UseHangfireServer(storage, options, ...);
    

    ServerFilterProvider是什么:

    public class ServerFilterProvider : IJobFilterProvider
    {
        public IEnumerable<JobFilter> GetFilters(Job job)
        {
            return new JobFilter[]
                       {
                           new JobFilter(new CaptureCultureAttribute(), JobFilterScope.Global, null),
                           new JobFilter(new ServerTenantFilter (), JobFilterScope.Global,  null),
                       };
        }
    }
    

    当您实例化BackgroundJobClient时,可以声明客户端筛选器。

    var client = new BackgroundJobClient(storage, new BackgroundJobFactory(new ClientFilterProvider());
    

    ClientFilterProvider 行为类似于 ServerFilterProvider,提供客户端过滤器。

    一个难点是在过滤器中可用的 TenantCurrentService。我猜这应该可以通过将工厂注入 FilterProviders 并将其链接到过滤器来实现。

    希望这能帮到您。


    感谢您的精彩回答!现在我能够使用 IClientFilter 在作业参数中打印 TenantId。我该如何从 IServerFilter 中检索当前的 ASP.NET Core DI ServiceScope(负责检索保存的作业参数),以便我可以调用 .GetRequiredService<ITenantCurrentService>().IdentifyTenant(tenantId) - Dekim
    @Dekim 我不知道。我不使用Aspnet Core的HF。也许你应该在你的FilterProvider中包含ServiceScope或者一个工厂,然后提供它给Filter。 - jbl
    @dekim 你解决了这一部分吗? - Rory

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接