我正在开发一个多租户 ASP.NET Core 7 MVC 应用程序,其中每个租户都有自己的数据库。连接字符串根据 URL 变化,每个 URL 对应不同的租户及其特定的数据库连接字符串。我需要集成 Hangfire 来处理后台作业,但我在配置 Hangfire 以使用基于租户的动态连接字符串方面面临挑战。此外,我需要处理 Hangfire 仪表板以显示特定于当前租户的数据。
这是我目前的设置: ASP.NET Core 7 MVC 多租户实施 不同租户的不同连接字符串,由 URL 决定 Hangfire 用于后台作业处理 我的问题: 我需要将 Hangfire 配置为动态地为每个租户使用正确的连接字符串。但是,我不确定如何在连接字符串根据 URL 变化的多租户环境中正确设置 Hangfire。我还需要配置 Hangfire 仪表板以仅显示当前租户的数据。
我尝试过的: 自定义中间件:我实现了自定义中间件来拦截请求并设置连接字符串。 动态 DbContext:尝试根据 HttpContext 中存储的连接字符串动态配置 DbContext。
示例代码: 租户中间件:
public class TenantResolutionMiddleware
{
private readonly RequestDelegate _next;
public TenantResolutionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
try
{
await tenantService.IdentifyTenant(context);
var tenant = await tenantService.GetCurrentTenantAsync(context);
if (tenant != null)
{
context.Items["ConnectionString"] = tenant.ConnectionString;
context.Items["TenantConnectionString"] = tenant.ConnectionString;
context.Items["TenantId"] = tenant.Id;
}
else
{
var defaultConnectionString = context.RequestServices.GetRequiredService<IConfiguration>().GetConnectionString("Web");
context.Items["ConnectionString"] = defaultConnectionString;
context.Items["TenantConnectionString"] = defaultConnectionString;
}
}
catch (Exception ex)
{
context.Response.StatusCode = 500; // Internal Server Error
await context.Response.WriteAsync("An error occurred while identifying the tenant.");
return;
}
await _next(context);
}
}
初始化租户数据库:
public static void InitializeTenantDatabases(this WebApplication app)
{
using var scope = app.Services.CreateScope();
var tenantService = scope.ServiceProvider.GetRequiredService<ITenantService>();
var tenants = tenantService.GetAllTenants();
foreach (var tenant in tenants)
{
using var tenantScope = app.Services.CreateScope();
var dbContext = tenantScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
dbContext.Database.SetConnectionString(tenant.ConnectionString);
dbContext.Database.Migrate();
var dataInitializers = tenantScope.ServiceProvider.GetServices<IDataInitializer>();
foreach (var dataInitializer in dataInitializers)
dataInitializer.InitializeData();
}
}
ITenant服务接口:
public interface ITenantService
{
Task IdentifyTenant(HttpContext context);
Task<Entities.Tenant.Tenant> GetCurrentTenantAsync(HttpContext context);
string GetCurrentTenantConnectionString();
Guid GetCurrentTenantId();
string GetCurrentTenantIdString();
Entities.Tenant.Tenant GetCurrentTenant();
Task<Entities.Tenant.Tenant> GetCurrentTenantAsyncCashing(HttpContext context);
string GetConnectionStringByTenantId(Guid tenantId);
string GetDatabaseProvider();
IEnumerable<Entities.Tenant.Tenant> GetAllTenants();
}
我在寻找什么: 如何配置 Hangfire 以根据 URL 为每个租户动态使用正确的连接字符串。 如何处理 Hangfire 仪表板以显示特定于当前租户的数据。 有关如何在多租户 ASP.NET Core 7 MVC 应用程序中正确实现 Hangfire 的示例或资源。 在将 Hangfire 与多租户集成时我应该注意的任何潜在陷阱或最佳实践。
我尝试实现自定义中间件来根据 URL 设置连接字符串并将其与 Hangfire 集成。我希望 Hangfire 动态地为每个租户使用正确的连接字符串,并使用各自的数据库为每个租户执行后台作业。
预期结果:
Hangfire 会动态地从中间件获取连接字符串,并为正确的租户数据库执行作业。 Hangfire 仪表板将显示特定于当前租户的数据。 实际结果:
Hangfire未使用动态连接字符串,后台作业无法连接到正确的租户数据库。 Hangfire 仪表板没有显示特定于租户的数据。
您可以使用几种不同的方法。第一个潜在问题是尝试对
static
使用 DbContexts
初始化。我建议为所有会话所依赖的身份验证/授权设置一个单独的有界DbContext
,其中包括检索相关租户连接字符串。特定于租户的应用程序 DbContext
的范围应限于 Web 请求甚至更短,这意味着应使用适用于当前会话的租户对其进行初始化。
您可以考虑以下三种方法之一:
引入 SessionService 帮助器之类的东西来包装当前经过身份验证的会话状态以注入到 DbContext 中。这里,DbContext 初始化将从 IoC 容器注入的 ISessionService 获取连接字符串,该容器将返回特定于当前会话用户的连接字符串。
配置 IoC 容器以按需构建 DbContext。这将取决于您使用的 IoC 容器,我个人使用 Autofac 是因为它的可配置性。因此,而不是单个可配置的连接字符串:
builder.RegisterType<AppDbContext>()
.WithParameter(new NamedParameter("connectionString", connectionString))
.AsSelf();
注册按需解决:
builder.Register<AppDbContext>(context =>
{ // However you want to fetch the connection string, from the container or a static Singleton, etc.
ISessionService sessionService = context.Resolve<ISessionService>();
var connectionString = sessionService.ConnectionString;
// TODO: Validate and handle if the session times out or doesn't return a connection string.
return new AppDbContext(connectionString);
});
DbContext
,而不是注入 IDbContextFactory
,它包装当前会话检查连接字符串,并根据需要使用所需的连接字符串初始化 DbContext
。