【问题标题】:How to get a Hangfire Job Parameter into the method that is executed by that job如何将 Hangfire 作业参数获取到该作业执行的方法中
【发布时间】:2021-12-18 06:11:39
【问题描述】:

我在 ASP.Net Core 5 Web 应用程序中安装了 Hangfire。 我按照 Hangfire 网站上的 Getting Started 指南进行了初始安装和配置,而且效果很好。

由于它是一个多租户应用程序(每个租户一个数据库),我需要能够在服务器上处理作业时连接到正确的数据库。

我遇到了this well explained post,据我所知,这正是我所需要的,但我不知道最后一步是如何从作业参数(租户标识)中获取值到方法中Hangfire 正在执行。

我已经创建了客户端过滤器

#region Hangfire Job Filters
#region Client filters
public class HfClientTenantFilter : IClientFilter
{
    private readonly IMultiTenant _multiTenant;
    public HfClientTenantFilter(IMultiTenant multiTenant)
    {
        _multiTenant = multiTenant;
    }
    public async void OnCreating(CreatingContext filterContext)
    {
        if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));

        MdxTenantConfig tenantConfig = await _multiTenant.GetTenantConfig();
        filterContext.SetJobParameter("TenantId", tenantConfig.Id);
    }

    public void OnCreated(CreatedContext filterContext)
    {
        if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
    }
}

public class HfClientFilterProvider : IJobFilterProvider
{
    private readonly IMultiTenant _multiTenant;
    public HfClientFilterProvider(IMultiTenant multiTenant) {
        _multiTenant = multiTenant;
    }
    public IEnumerable<JobFilter> GetFilters(Job job)
    {
        return new JobFilter[]
        {
            new JobFilter(new CaptureCultureAttribute(), JobFilterScope.Global, null),
            new JobFilter(new HfClientTenantFilter(_multiTenant),JobFilterScope.Global, null)
        };
    }
}
#endregion Client filters

和服务器过滤器:

#region Server filters
public class HfServerTenantFilter : IServerFilter
{
    private readonly IHfTenantProvider _hfTenantProvider;
    public HfServerTenantFilter(IHfTenantProvider hfTenantProvider)
    {
        _hfTenantProvider = hfTenantProvider;
    }

    public void OnPerforming(PerformingContext filterContext)
    {
        if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));

        var tenantId = filterContext.GetJobParameter<string>("TenantId");
        // need to get the tenantId passed to the method that calls the creation of the DbContext
        _hfTenantProvider.HfSetTenant(tenantId);
    }

    public void OnPerformed(PerformedContext filterContext)
    {
        if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
    }
}

public class HfServerFilterProvider : IJobFilterProvider
{
    private readonly IHfTenantProvider _hfTenantProvider;
    public HfServerFilterProvider(IHfTenantProvider hfTenantProvider)
    {
        _hfTenantProvider = hfTenantProvider;
    }
    public IEnumerable<JobFilter> GetFilters(Job job)
    {
        return new JobFilter[]
        {
            new JobFilter(new CaptureCultureAttribute(), JobFilterScope.Global, null),
            new JobFilter(new HfServerTenantFilter(_hfTenantProvider), JobFilterScope.Global,  null),
        };
    }
}
#endregion Server filters
#endregion Hangfire Job Filters

在启动中附加服务器过滤器

services.AddHangfireServer(opt => {
                opt.FilterProvider = new HfServerFilterProvider(new HfTenantProvider());  
});

在调试中执行时,我看到参数TenantID 在客户端过滤器中设置正确,并且服务器过滤器也正确检索。
我现在正在尝试使该值可用于正在执行但尚未成功的方法。
我尝试使用范围服务:

#region Scoped Tenant provider service

public interface IHfTenantProvider {
    void HfSetTenant(string TenantCode);
    string HfGetTenant();
}

public class HfTenantProvider: IHfTenantProvider
{
    public string HfTenantCode;

    public void HfSetTenant(string TenantCode)
    {
        HfTenantCode = TenantCode;
    }

    public string HfGetTenant()
    {
        return HfTenantCode;
    }
}
#endregion Scoped Tenant provider service

在启动中:

services.AddScoped<IHfTenantProvider, HfTenantProvider>();

在服务器过滤器的 OnPerforming 方法中,我将 Scoped 服务中的值设置为从作业参数中检索到的值(完整代码见上文),正如我在调试中看到的那样。

_hfTenantProvider.HfSetTenant(tenantId);

这是正在安排的测试方法:

public bool HfTest()
{
    try
    {
        BackgroundJobClient jobClient = GetJobClient();
        jobClient.Enqueue<MdxMetaCRUD>(crud => crud.HfTest());
    }
    catch (Exception)
    {
        return false;
    }
    return true;
}

这是我从 Scoped 服务中检索值的方法本身,但是 null:

public async Task HfTest()
{
    string tenant =_hfTenantProvider.HfGetTenant();
    using (var _dbContext = _contextHelper.CreateContext(true, tenant))
    {
        Entity entity = new()
        {
            Name = "HFtest",
            Description = tenant,
            DefaultAuditTrackRetention = 1
        };
        await _dbContext.Entities.AddAsync(entity);
    }
}

ContextHelper 返回一个新的 DbContext:

public ApplicationDbContext CreateContext(bool backgroundProcess, string backgroundTenant)
{
    return new ApplicationDbContext(_multiTenant, backgroundProcess, backgroundTenant);
}

DbContext 有一个覆盖来检索租户的连接字符串(在用户上下文中工作正常)。如果 Hangfire 正在执行作业,我正在尝试将 TenantId 传递给 _backgroundTenant

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    try
    {
        MdxTenantConfig tenantConfig = Task.Run(() => _multiTenant.GetTenantConfig(_backgroundProcess, _backgroundTenant)).Result;
        optionsBuilder.UseSqlServer(tenantConfig.ConnectionString);
        base.OnConfiguring(optionsBuilder);
    }
    catch //(Exception)
    {
        throw;
    }
}

我希望正在启动的作业(正在检索服务器过滤器)和正在执行的作业在同一范围内(请求),但似乎不是。
我对范围服务做错了什么还是我的方法有误?我看到一些文章提到了“工厂”,但这超出了我的知识范围(目前)。

【问题讨论】:

    标签: c# dependency-injection asp.net-core-mvc multi-tenant hangfire


    【解决方案1】:

    您可以尝试像这样实现您的HfTenantProvider

    public class HfTenantProvider : IHfTenantProvider
    {
        private static System.Threading.AsyncLocal<string> HfTenantCode { get; } = new System.Threading.AsyncLocal<string>();
    
        public void HfSetTenant(string TenantCode)
        {
            HfTenantCode.Value = TenantCode;
        }
    
        public string HfGetTenant()
        {
            return HfTenantCode.Value;
        }
    }
    

    我同意这使得HfTenantProvider 的范围界定有些无用。 此外,您可以在服务器过滤器的OnPreformed 方法中调用HfSetTenant(null)。我认为这更干净,即使这不会有太大的区别。

    【讨论】:

    • 谢谢,完美!与此同时,我找到了一个更简单的解决方案(对于我的场景),它不涉及任何作业参数/过滤器。我也会为正在寻找解决方案的其他人发布它,但我会接受您的回答,因为它是我最初问题的正确解决方案。我会做一些阅读以充分理解您的解决方案:)
    【解决方案2】:

    如上所述,@jbl 的答案是正确的,并且完全按照我在原始问题中的要求工作。我希望这对其他人有用。
    然而,我找到了一个更简单的解决方案,其中我不需要作业参数或过滤器,并且适合我的场景。正如我所看到的,在多租户环境中实施 Hangfire 时,很多人都在寻求指导,我也发布了这个解决方案,希望它也能对一些人有所帮助。

    在我的情况下,作业的调度是由用户通过 UI 完成的。这意味着我总是在安排工作时知道租户。
    所以我只是将租户 ID 作为参数传递给计划的方法。
    而不是将作业安排为:

    backgroundClient.Enqueue<MyClass>(c => c.MyMethod(taskId)):
    

    我将工作安排如下:

    string tenantCode = _multiTenant.GetTenant();
    backgroundClient.Enqueue<MyClass>(c => c.MyMethod(taskId, tenantCode)):
    

    _multiTenant 是我从 HttpContextAccessor 检索租户 ID 的实现。

    要针对正确的数据库上下文执行该方法,我只需要在创建 DbContext 时传递 tenantId

    public async Task MyMethod(int taskId, string tenantCode)
    {
        using (var _dbContext = _contextHelper.CreateContext(true, tenantCode))
        {
            ...
    

    我的 DbContext 的覆盖 OnConfiguring 然后可以直接获取租户配置(包括数据源字符串)。

    【讨论】:

      猜你喜欢
      • 2022-05-20
      • 1970-01-01
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-19
      • 1970-01-01
      相关资源
      最近更新 更多