You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在SignalR Hub的OnConnected/OnDisconnected中注入租户信息?

解决SignalR Hub中获取多租户TenantId的问题

你的核心问题是SignalR Hub的生命周期独立于HTTP请求作用域,因此无法直接注入仅在请求范围内可用的ITenancyContext<ApplicationTenant>。下面提供两种可行的解决方案,重点讲解你提到的授权声明方式:

方法一:通过授权声明传递TenantId(推荐)

这种方式不需要依赖作用域服务,直接从用户身份声明中提取TenantId,是SignalR多租户场景的标准做法:

  1. 登录时添加TenantId声明
    在用户登录生成身份凭证(比如JWT或Cookie)时,将TenantId加入自定义声明:

    // 示例:登录逻辑中构建ClaimsIdentity
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim("TenantId", tenantId.ToString()) // 自定义TenantId声明
    };
    var identity = new ClaimsIdentity(claims, JwtBearerDefaults.AuthenticationScheme);
    
  2. 配置SignalR连接要求授权
    在Program.cs/Startup.cs中,确保SignalR Hub仅允许授权用户连接:

    app.MapHub<NotificationHub>("/notificationHub")
       .RequireAuthorization();
    
  3. 修改Hub从声明中获取TenantId
    移除对ITenancyContext的依赖,直接从Context.User中提取TenantId:

    public sealed class NotificationHub : Hub
    {
        readonly ILogger<NotificationHub> _logger;
    
        public NotificationHub(ILogger<NotificationHub> logger) => _logger = logger;
    
        public override async Task OnConnectedAsync()
        {
            // 提取并验证TenantId声明
            var tenantIdClaim = Context.User.FindFirst("TenantId");
            if (tenantIdClaim == null || !Guid.TryParse(tenantIdClaim.Value, out var tenantId))
            {
                // 无有效TenantId时直接断开连接
                await Context.AbortAsync();
                _logger.LogWarning("Connection {ConnectionId} rejected: No valid TenantId claim", Context.ConnectionId);
                return;
            }
    
            await Groups.AddToGroupAsync(Context.ConnectionId, tenantId.ToString());
            _logger.LogInformation("{ConnectionId} connected. TenantId: {TenantId}", Context.ConnectionId, tenantId);
            await base.OnConnectedAsync();
        }
    
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            var tenantIdClaim = Context.User.FindFirst("TenantId");
            if (tenantIdClaim != null && Guid.TryParse(tenantIdClaim.Value, out var tenantId))
            {
                await Groups.RemoveFromGroupAsync(Context.ConnectionId, tenantId.ToString());
                _logger.LogInformation("{ConnectionId} disconnected. TenantId: {TenantId}", Context.ConnectionId, tenantId);
            }
            await base.OnDisconnectedAsync(exception);
        }
    }
    

方法二:手动创建作用域获取ITenancyContext

如果必须复用现有ITenancyContext逻辑,可以通过IServiceScopeFactory手动创建作用域来解析服务:

public sealed class NotificationHub : Hub
{
    readonly ILogger<NotificationHub> _logger;
    readonly IServiceScopeFactory _scopeFactory;

    public NotificationHub(ILogger<NotificationHub> logger, IServiceScopeFactory scopeFactory) 
        => (_logger, _scopeFactory) = (logger, scopeFactory);

    public override async Task OnConnectedAsync()
    {
        // 创建临时作用域解析ITenancyContext
        using var scope = _scopeFactory.CreateScope();
        var tenancyContext = scope.ServiceProvider.GetRequiredService<ITenancyContext<ApplicationTenant>>();
        var tenantId = tenancyContext.Tenant.Id;

        await Groups.AddToGroupAsync(Context.ConnectionId, tenantId.ToString());
        _logger.LogInformation("{ConnectionId} connected. TenantId: {TenantId}", Context.ConnectionId, tenantId);
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception? exception)
    {
        using var scope = _scopeFactory.CreateScope();
        var tenancyContext = scope.ServiceProvider.GetRequiredService<ITenancyContext<ApplicationTenant>>();
        var tenantId = tenancyContext.Tenant.Id;

        await Groups.RemoveFromGroupAsync(Context.ConnectionId, tenantId.ToString());
        _logger.LogInformation("{ConnectionId} disconnected. TenantId: {TenantId}", Context.ConnectionId, tenantId);
        await base.OnDisconnectedAsync(exception);
    }
}

两种方案对比

  • 方法一:更符合SignalR的设计理念,依赖身份授权体系,无需额外管理作用域,稳定性更高,是优先推荐的方案。
  • 方法二:适合必须复用现有ITenancyContext逻辑的场景,但要注意作用域的正确释放(使用using块),避免资源泄漏。

内容的提问来源于stack exchange,提问作者nop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 06:43:19