I've got an asp.net 5 web app using standard .net Dependency Injection. As I understand it, out of the box Hangfire
will use the same dependencies for instantiating jobs as MVC will for controllers etc. I'd like to inject a different dependency when instantiating jobs. How can I do this?
e.g. some classes have dependencies on IHttpContextAccessor
so I want to provide an alternative for use within hangfire jobs that will get its state from serialized job parameters instead.
I see some discussion here of complex things that sounds like what I need ... but I'd love a simple example :-)
I ended up not using dependency injection to achieve this different behaviour. Instead I changed the classes that use IHttpContentAccessor
to alternatively derive the 'tenant' from state set within my Hangfire job methods.
IHttpContentAccessor
to get info from the current request I first look if there is a current request to get tenant info, and if not I check for that scoped object that's only set during hangfire jobs.Some example code:
// 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));
}
}