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

SignalR在线用户追踪实现遇连接断开及ArgumentNullException问题求助

问题定位

报错ArgumentNullException Value cannot be null (Parameter key)的核心原因是传入PresenceTracker的username为null,导致操作Dictionary<string, List<string>>时使用了不允许的null作为key(Dictionary的key不能为null)。

具体排查与修复步骤

1. 验证用户认证状态与GetUsername()扩展方法

首先确认Context.User.GetUsername()是否返回null:

  • 在PresenceHub的OnConnectedAsync开头添加校验逻辑:
    public override async Task OnConnectedAsync()
    {
        var isAuthenticated = Context.User.Identity?.IsAuthenticated ?? false;
        var username = Context.User.GetUsername();
    
        if (!isAuthenticated || string.IsNullOrEmpty(username))
        {
            await Context.AbortAsync();
            return;
        }
        
        // 原有业务代码
        await _tracker.UserConnected(username, Context.ConnectionId);
        await Clients.Others.SendAsync("UserIsOnline", username);
    
        var currentUsers = await _tracker.GetOnlineUsers();
        await Clients.All.SendAsync("GetOnlineUsers", currentUsers);
    }
    
  • 检查GetUsername()扩展方法的实现,确保能正确从Claims中获取用户名:
    public static class ClaimsPrincipalExtensions
    {
        public static string GetUsername(this ClaimsPrincipal user)
        {
            // 匹配JWT生成时使用的Claim类型,比如ClaimTypes.Name或自定义的"username"
            var username = user.FindFirstValue(ClaimTypes.Name) ?? user.FindFirstValue("username");
            if (string.IsNullOrEmpty(username))
                throw new InvalidOperationException("JWT中未包含用户名Claim");
            return username;
        }
    }
    

2. 给PresenceTracker添加空值防护

避免因空参数导致崩溃:

public Task UserConnected(string username, string connectionId)
{
    if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(connectionId))
        return Task.CompletedTask;

    lock (OnlineUsers)
    {
        if (OnlineUsers.ContainsKey(username))
        {
            OnlineUsers[username].Add(connectionId);
        }
        else
        {
            OnlineUsers.Add(username, new List<string>{ connectionId });
        }
    }

    return Task.CompletedTask;
}

public Task UserDisconnected(string username, string connectionId)
{
    if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(connectionId))
        return Task.CompletedTask;

    lock (OnlineUsers)
    {
        if(!OnlineUsers.ContainsKey(username)) return Task.CompletedTask;

        OnlineUsers[username].Remove(connectionId);

        if (OnlineUsers[username].Count == 0)
        {
            OnlineUsers.Remove(username);
        }
    }

    return Task.CompletedTask;
}

3. 检查前端Token传递与有效性

  • 确认user.token是有效JWT,可通过解析工具查看是否包含正确的用户名Claim;
  • 校验SignalR连接的token传递逻辑:
    accessTokenFactory: () => {
        console.log(user.token); // 输出确认token是否存在
        return user.token || '';
    },
    

4. 调整中间件顺序(关键)

ASP.NET Core中间件顺序必须符合规范,正确顺序如下:

// 1. 配置CORS
app.UseCors(builder =>
{
    builder.WithOrigins("https://localhost:4200")
           .AllowCredentials()
           .AllowAnyMethod()
           .AllowAnyHeader();
});

// 2. 启用认证
app.UseAuthentication();
// 3. 启用授权
app.UseAuthorization();

// 4. 启用WebSockets(SignalR依赖,可省略,SignalR会自动处理)
app.UseWebSockets();

// 5. 映射SignalR Hub
app.MapHub<PresenceHub>("/hubs/presence");

5. 修正JWT认证配置的矛盾点

原配置中ValidateIssuer设为false但指定了ValidIssuer,建议统一配置:

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Token:Key"])),
    ValidIssuer = builder.Configuration["Token:Issuer"],
    ValidateIssuer = true, // 改为true,确保Issuer校验生效
    ValidateAudience = false
};

6. 修复前端事件监听的拼写错误

前端GetOnlineUsers事件名多了一个空格,与后端发送的名称不匹配:

// 错误写法:事件名带空格
this.hubConnection.on('GetOnlineUsers ', usernames => {
  this.onlineUsersSource.next(usernames);
})
// 修正为:
this.hubConnection.on('GetOnlineUsers', usernames => {
  this.onlineUsersSource.next(usernames);
})

总结

最可能的根源是用户认证未通过导致Context.User.GetUsername()返回null,或者JWT中缺少用户名Claim。按上述步骤逐步排查,先确认认证状态和用户名获取逻辑,再修复后端空值防护、中间件顺序及前端拼写问题,即可解决连接后立即断开的问题。

内容的提问来源于stack exchange,提问作者Jean De La Hone

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:25:51