我已经在一个ASP.Net Core 5的Web应用中安装了Hangfire,并按照Hangfire网站上的入门指南进行了操作,初始安装和配置很快就完成了,并且可以正常工作。
由于这是一个多租户应用程序(每个租户一个数据库),因此我需要在服务器处理作业时能够连接到正确的数据库。
我看到了这篇非常详细的文章,正是我需要的,但我无法弄清楚最后一步是如何将作业参数(租户标识)的值传递给Hangfire正在执行的方法。
我已经创建了客户端过滤器。
由于这是一个多租户应用程序(每个租户一个数据库),因此我需要在服务器处理作业时能够连接到正确的数据库。
我看到了这篇非常详细的文章,正是我需要的,但我无法弄清楚最后一步是如何将作业参数(租户标识)的值传递给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
方法中,我将从作业参数中检索到的值设置为作用域服务中的值(完整代码请参见上文),在调试中可以看到这个方法是有效的。
_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执行的作业,我正在尝试将传递到_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;
}
}
我原以为(服务器过滤器被检索时)启动的工作和执行的工作在同一范围内(请求),但事实并非如此。
我是否对作用域服务做错了什么或者我的方法是错误的?我看到有些文章提到了“工厂”,但那超出了我的知识范围(目前为止)。