问题
I got a SaaS project that needs the use Hangfire. We already implemented the requirements to identify a tenant.
Architecture
- Persistence Layer
- Each tenant has it's own database
- .NET Core
- We already have a service
TenantCurrentService
which returns the ID of the tenant, from a list of source [hostname, query string, etc] - We already have a
DbContextFactory
for Entity Framework which return a DB context with the correct connection string for the client - We are currently using ASP.NET Core DI (willing to change if that helps)
- We already have a service
- Hangfire
- Using single storage (eg: Postgresql), no matter the tenant count
- Execute the job in an appropriate Container/ServiceCollection, so we retrieve the right database, right settings, etc.
The 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
- Currently, we have been able to store tenantId retrieved from our Service using a
IClientFilter
. - How can I retrieve my current ASP.NET Core DI ServiceScope from IServerFilter (which is responsible to retrieve the saved Job Parameters), so I can call .GetRequiredService().IdentifyTenant(tenantId)
Is there any good article regarding this matter / or any tips that you guys can provide?
回答1:
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.
来源:https://stackoverflow.com/questions/57394712/hangfire-multi-tenant-asp-net-core-resolving-the-correct-tenant