从 SignalR 核心集线器中的范围请求 DbContext,将连接字符串传递给它
Requesting a DbContext from a scope in a SignalR Core hub, passing it a connection string
我有一个带有 SignalR 集线器的 ASP .Net Core 2.2 Web API。
当API从客户端收到一条消息时,它需要将这条消息保存到数据库中。它按如下方式执行此操作:
SignalR 中心:
public class ChatHub : Hub
{
public async Task SendMessageToGroup(int clientId, int groupName, string message)
{
await SaveMessage(clientId, groupName, message);
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
private async Task<bool> SaveMessage(int clientId, string groupName, string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();
Message newMessage = new Message()
{
Message = message,
GroupName = groupName,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(pwMessage);
dbContext.SaveChanges();
}
return true;
}
}
除了这是一个 multi-tenant 应用程序之外,一切都会很好。通常,当客户端使用 HTTP 请求调用 API 的控制器方法时,客户端会通过 "TenantId" header 发送每个请求。然后我有拦截此请求的中间件,从 header 中获取 TenantId,调用服务以使用 tenantId 检索此租户,并将租户 object 保存在 HttpContext 中。然后,在 DbContext 的 OnConfiguring() 方法上,我使用此 Tenant Object(存储在 HttpContext 中)将 dbContext 的 connectionString 设置为该租户使用的任何数据库。所以:
中间件:
public class TenantIdentifier
{
private readonly RequestDelegate _next;
public TenantIdentifier(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
Tenant tenant = await GetTenant(tenantId);
httpContext.Items["Tenant"] = tenant;
await _next.Invoke(httpContext);
}
}
DbContext.cs:
public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}
public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
optionsBuilder.UseMySql(connectionString);
}
现在,当客户端调用 SignalR 集线器时,我在集线器中创建了一个新范围并请求 DbContext,它的连接字符串为空。这似乎是因为,与 HTTP 请求不同,调用 SignalR 集线器不会触发中间件(负责识别租户)
我如何在从范围请求 DbContext 时手动将连接字符串传递给它,而不是依赖它来尝试在 OnConfiguring() 事件中生成 connectionString(这不起作用)
希望这是有道理的:/谢谢
如果您将 IHttpContextAccessor 添加到您的 Hub class 构造函数 - 您是否能够在那里访问当前上下文(和 headers)?
public class ChatHub : Hub
{
private IHttpContextAccessor currentContext;
public ChatHub(IHttpContextAccessor currentContext)
{
this.currentContext = currentContext;
}
}
当然记得也要在DI中注册HttpContextAccessor:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}
我有一个带有 SignalR 集线器的 ASP .Net Core 2.2 Web API。 当API从客户端收到一条消息时,它需要将这条消息保存到数据库中。它按如下方式执行此操作:
SignalR 中心:
public class ChatHub : Hub
{
public async Task SendMessageToGroup(int clientId, int groupName, string message)
{
await SaveMessage(clientId, groupName, message);
await Clients.Group(groupName).SendAsync("ReceiveMessage", message);
}
private async Task<bool> SaveMessage(int clientId, string groupName, string message)
{
using (var scope = _serviceProvider.CreateScope())
{
var dbContext = scope.ServiceProvider.GetRequiredService<TenantContext>();
Message newMessage = new Message()
{
Message = message,
GroupName = groupName,
Timestamp = DateTime.Now
};
dbContext.Messages.Add(pwMessage);
dbContext.SaveChanges();
}
return true;
}
}
除了这是一个 multi-tenant 应用程序之外,一切都会很好。通常,当客户端使用 HTTP 请求调用 API 的控制器方法时,客户端会通过 "TenantId" header 发送每个请求。然后我有拦截此请求的中间件,从 header 中获取 TenantId,调用服务以使用 tenantId 检索此租户,并将租户 object 保存在 HttpContext 中。然后,在 DbContext 的 OnConfiguring() 方法上,我使用此 Tenant Object(存储在 HttpContext 中)将 dbContext 的 connectionString 设置为该租户使用的任何数据库。所以:
中间件:
public class TenantIdentifier
{
private readonly RequestDelegate _next;
public TenantIdentifier(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string tenantId = httpContext.Request.Headers["tenantId"].FirstOrDefault();
Tenant tenant = await GetTenant(tenantId);
httpContext.Items["Tenant"] = tenant;
await _next.Invoke(httpContext);
}
}
DbContext.cs:
public TenantContext(DbContextOptions<TenantContext> options) : base(options)
{
}
public TenantContext(DbContextOptions<TenantContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
Tenant tenant = (Tenant)_httpContextAccessor.HttpContext.Items["Tenant"];
string connectionString = $"server={tenant.DbUrl};user id={tenant.DbUserName};Pwd={tenant.DbPassword};database={tenant.DbName};persistsecurityinfo=True;TreatTinyAsBoolean=false";
optionsBuilder.UseMySql(connectionString);
}
现在,当客户端调用 SignalR 集线器时,我在集线器中创建了一个新范围并请求 DbContext,它的连接字符串为空。这似乎是因为,与 HTTP 请求不同,调用 SignalR 集线器不会触发中间件(负责识别租户)
我如何在从范围请求 DbContext 时手动将连接字符串传递给它,而不是依赖它来尝试在 OnConfiguring() 事件中生成 connectionString(这不起作用)
希望这是有道理的:/谢谢
如果您将 IHttpContextAccessor 添加到您的 Hub class 构造函数 - 您是否能够在那里访问当前上下文(和 headers)?
public class ChatHub : Hub
{
private IHttpContextAccessor currentContext;
public ChatHub(IHttpContextAccessor currentContext)
{
this.currentContext = currentContext;
}
}
当然记得也要在DI中注册HttpContextAccessor:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddHttpContextAccessor();
services.AddTransient<IUserRepository, UserRepository>();
}