从 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>();
}