scope - 从 SignalR 核心集线器中的范围请求 DbContext,向其传递连接字符串
问题描述
我有一个带有 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;
}
}
除了这是一个多租户应用程序之外,一切都会很好。通常,当客户端使用 HTTP 请求调用 API 的控制器方法时,客户端会通过每个请求的“TenantId”标头发送。然后我有中间件拦截这个请求,从标头中获取 TenantId,调用服务以使用tenantId 检索此租户,并将租户对象保存在 HttpContext 中。然后,在 DbContext 的 OnConfiguring() 方法上,我使用此租户对象(存储在 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 类构造函数中 - 您是否能够访问那里的当前上下文(和标头)?
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>();
}
推荐阅读
- bash - 替换文件夹中所有文件的所有占位符
- python - 将字符串字典转换为字典格式
- python-2.7 - 无法在主机上部署 flask-python 应用程序 - IP 地址
- c# - Bootstrap - 回发到控制器后显示确认模式
- python - 我可以在实时视频中找到对象的 hsv 范围吗?
- gdb - 如何获取 LLDB 中所有线程的回溯?
- javascript - 使用 Laravel 和 AJAX 根据另一个输入的值显示其他输入
- javascript - P5.js 对象不留下痕迹
- gitlab-ci - 我可以更改默认生产分支和/或集成分支吗?
- node.js - Puppeteer:为什么对象在 page.evaluate 函数中返回未定义?