ASP.NET核心的Hangfire依赖项注入:在处理Hangfire作业时注入不同的对象



我有一个使用标准.net依赖注入的asp.net 5 web应用程序。据我所知,开箱即用的Hangfire将使用与MVC对控制器等相同的依赖关系来实例化作业。我想在实例化作业时注入不同的依赖关系。我该怎么做?

例如,有些类依赖于IHttpContextAccessor,所以我想提供一种在hangfire作业中使用的替代方案,它将从序列化的作业参数中获取其状态。

我在这里看到一些关于复杂事情的讨论,听起来像是我需要的。。。但我喜欢一个简单的例子:-(

我最终没有使用依赖项注入来实现这种不同的行为。相反,我更改了使用IHttpContentAccessor的类,以从Hangfire作业方法中的状态集派生"租户"。

  • 在我的作业方法中,我首先根据作业方法的参数在"Scoped"对象中设置租户
  • 在使用IHttpContentAccessor从当前请求中获取信息的类中,我首先查看是否存在获取租户信息的当前请求,如果没有,我将检查仅在挂起作业期间设置的作用域对象
  • 在我的工作方法中,我不使用构造函数依赖项注入。相反,我在作业方法中使用服务定位器(anti(模式。这意味着在请求依赖于租户状态的对象之前,我可以先设置租户状态

一些示例代码:

// A service for getting current Tenant info
public class TenantAccessor : ITenantAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
// Here's a method I call from everywhere in my system 
// when I want the current domain name, which for me 
// identifies the tenants since they access it at 
// https://customername.myapplication.com. 
// Nowhere else uses _httpContextAccessor since that 
// won't work if called from within a Hangfire job. 
public string GetTenantDomain()
{ 
// If there's an http context then use it: 
if (_httpContextAccessor.HttpContext != null)
return _httpContextAccessor.HttpContext.Request.Host.Host;
// Otherwise return this string value, if set
return _hangfireTenantInfo.TenantDomain;
}
}
public class MyHangfireJobs
{ 
// All the methods I call from Hangfire look similar to this: 
public async Task DoStuffInBackground(
string tenantDomain,  // 
string someOtherParameter)
{ 
// First set this string value so other services
// can get the tenant's domain.
var hangfireTenantInfo = _serviceProvider.GetRequiredService<HangfireTenantInfo>();
hangfireTenantInfo.TenantDomain = tenantDomain;
// Now all the normal code in the method
// Some of this code will call services that use
// TenantAccessor.GetTenantDomain()

...
// In these job methods I use GetRequiredService() instead
// of constructor injection, so creation of those 
// services happens after setting TenantDomain, e.g.: 
var someService = _serviceProvider.GetRequiredService<SomeImportantService>();
someService.DoTheStuff();
} 
} 
// Just a class that wraps a string variable 
public class HangfireTenantInfo
{
public string TenantDomain { get; set; }
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
... 
// Somewhere in my startup service registration code:
// Register these as scoped so there's one per request. 
services.AddScoped<HangfireTenantInfo>();
services.AddScoped<ITenantAccessor, TenantAccessor>();
...
} 
...
} 

[ApiController]
[Route("api/blah")]
public class BlahController : ControllerBase
{
readonly ITenantAccessor _tenantAccessor;
readonly IBackgroundJobClient _backgroundJobClient;
// Nothing special here: constructor injection for services.
public BlahController(ITenantAccessor tenantAccessor,
IBackgroundJobClient backgroundJobClient)
{
_tenantAccessor = tenantAccessor;
_backgroundJobClient = backgroundJobClient;
}
// This is what a controller method might look like that 
// runs background hangfire jobs
[HttpPost("do/stuff/{aParam}")]
public Task DoStuff(string aParam)
{
// Get the current request's Host (which will come 
// from httpContext since we're within a request).
var currentDomain = _tenantAccessor.GetTenantDomain();
// Run a background job, passing in tenant's domain
_backgroundJobClient.Enqueue<MyHangfireJobs>(x =>
x.DoStuffInBackground(currentDomain, aParam));
}
}

最新更新