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
相关产品推荐
相关产品推荐

