I got a SaaS project that needs the use Hangfire. We already implemented the requirements to identify a tenant.
Architecture
TenantCurrentService
which returns the ID of the tenant, from a list of source [hostname, query string, etc]DbContextFactory
for Entity Framework which return a DB context with the correct connection string for the clientThe problem
I'm trying to stamp a TenantId to a job, retrieved from TenantCurrentService
(which is a Scoped service).
When the job then gets executed, we need to retrieve the TenantId
from the Job and store it in HangfireContext
, so then the TenantCurrentService
knows the TenantId retrieved from Hangfire. And from there, our application layer will be able to connect to the right database from our DbContextFactory
Current state
IClientFilter
.Is there any good article regarding this matter / or any tips that you guys can provide?
First, you need to be able to set the TenantId
in your TenantCurrentService
.
Then, you can rely on filters :
client side (where you enqueue jobs)
public class ClientTenantFilter : IClientFilter
{
public void OnCreating(CreatingContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException(nameof(filterContext));
filterContext.SetJobParameter("TenantId", TenantCurrentService.TenantId);
}
}
and server side (where the job is dequeued).
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;
}
}
The server filter can be declared when you configure your server through an IJobFilterProvider
:
var options = new BackgroundJobServerOptions
{
Queues = ...,
FilterProvider = new ServerFilterProvider()
};
app.UseHangfireServer(storage, options, ...);
where ServerFilterProvider is :
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),
};
}
}
The client filter can be declared when you instantiate a BackgroundJobClient
var client = new BackgroundJobClient(storage, new BackgroundJobFactory(new ClientFilterProvider());
where ClientFilterProvider
behaves as ServerFilterProvider
, delivering client filter
A difficulty may be to have the TenantCurrentService available in the filters. I guess this should be achievable by injecting factories in the FilterProviders and chain it to the filters.
I hope this will help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With