首页 > 解决方案 > 从 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(这不起作用)

希望这是有道理的:/谢谢

标签: scopeasp.net-core-webapidbcontextsignalr-hubasp.net-core-signalr

解决方案


如果您将 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>();
}

推荐阅读