ASP.NET Core 6 SignalR Hub中如何持久存储ConnectionId与用户关联数据
解决SignalR Hub中ConnectionId与用户关联持久化问题
你的问题核心是Hub实例为Transient生命周期,每次方法调用都会新建实例,导致实例级的Dictionary无法跨实例保存数据。下面提供两种可行的字典存储方案:
方案一:使用静态线程安全字典
直接将字典改为静态的ConcurrentDictionary(保证多线程环境下的操作安全),所有Hub实例共享这个静态字典:
修改后的ChatHub代码:
public static class MessageContext { public static string RECEIVE = "ReceiveMessage"; public static string REGISTER = "Register"; public static string SEND = "SendMessage"; } public class ChatHub : Hub { public const string HUBURL = "/api/ChatSignal"; // 静态线程安全字典,所有Hub实例共享 private static readonly ConcurrentDictionary<string, string> _userContext = new ConcurrentDictionary<string, string>(); public override Task OnConnectedAsync() { var connectionId = Context.ConnectionId; var username = Context.User.Identity.Name; // 尝试添加连接与用户的关联 _userContext.TryAdd(connectionId, username); await Groups.AddToGroupAsync(connectionId, username); return base.OnConnectedAsync(); } public async Task SendAll(string user, string message) { var connectionId = Context.ConnectionId; message = HttpUtility.HtmlEncode(message); // 获取当前连接对应的用户名 if (_userContext.TryGetValue(connectionId, out var username)) { await Clients.All.SendAsync(MessageContext.RECEIVE, username, message); } } public Task SendMessage(string sender, string receiver, string message) { var connectionId = Context.ConnectionId; message = HttpUtility.HtmlEncode(message); if (_userContext.TryGetValue(connectionId, out var username)) { return Clients.Group(receiver).SendAsync(MessageContext.RECEIVE, username, message); } return Task.CompletedTask; } public override async Task OnDisconnectedAsync(Exception e) { var connectionId = Context.ConnectionId; // 移除关联关系 _userContext.TryRemove(connectionId, out _); await base.OnDisconnectedAsync(e); } }
说明:
ConcurrentDictionary是.NET提供的线程安全字典,避免多线程并发操作时出现异常或数据错乱;- 静态成员属于类本身,所有ChatHub实例都会访问同一个字典实例,解决了Transient实例的数据共享问题。
方案二:注入单例服务存储关联(推荐)
如果项目遵循依赖注入设计原则,可创建一个单例服务来管理用户连接关系,比静态字典更易扩展和测试:
1. 创建用户连接管理服务
public class UserConnectionManager { private readonly ConcurrentDictionary<string, string> _connectionToUser = new ConcurrentDictionary<string, string>(); /// <summary> /// 添加连接与用户的关联 /// </summary> public bool AddConnection(string connectionId, string username) { return _connectionToUser.TryAdd(connectionId, username); } /// <summary> /// 移除连接的关联 /// </summary> public bool RemoveConnection(string connectionId) { return _connectionToUser.TryRemove(connectionId, out _); } /// <summary> /// 根据连接ID获取用户名 /// </summary> public string GetUsernameByConnectionId(string connectionId) { _connectionToUser.TryGetValue(connectionId, out var username); return username; } }
2. 注册单例服务
在Program.cs中添加服务注册:
builder.Services.AddSingleton<UserConnectionManager>();
3. 修改ChatHub注入服务
public class ChatHub : Hub { public const string HUBURL = "/api/ChatSignal"; private readonly UserConnectionManager _connectionManager; // 构造函数注入单例服务 public ChatHub(UserConnectionManager connectionManager) { _connectionManager = connectionManager; } public override Task OnConnectedAsync() { var connectionId = Context.ConnectionId; var username = Context.User.Identity.Name; _connectionManager.AddConnection(connectionId, username); await Groups.AddToGroupAsync(connectionId, username); return base.OnConnectedAsync(); } public async Task SendAll(string user, string message) { var connectionId = Context.ConnectionId; message = HttpUtility.HtmlEncode(message); var username = _connectionManager.GetUsernameByConnectionId(connectionId); if (!string.IsNullOrEmpty(username)) { await Clients.All.SendAsync(MessageContext.RECEIVE, username, message); } } public Task SendMessage(string sender, string receiver, string message) { var connectionId = Context.ConnectionId; message = HttpUtility.HtmlEncode(message); var username = _connectionManager.GetUsernameByConnectionId(connectionId); if (!string.IsNullOrEmpty(username)) { return Clients.Group(receiver).SendAsync(MessageContext.RECEIVE, username, message); } return Task.CompletedTask; } public override async Task OnDisconnectedAsync(Exception e) { var connectionId = Context.ConnectionId; _connectionManager.RemoveConnection(connectionId); await base.OnDisconnectedAsync(e); } }
说明:
- 单例服务由DI容器管理,生命周期与应用一致,所有Hub实例共享同一个服务实例;
- 这种方式解耦了Hub与连接管理逻辑,后续如果需要扩展(比如持久化到Redis、添加用户多连接支持),只需修改服务类即可。
注意事项
- 无论哪种方案,都必须使用线程安全的集合(如
ConcurrentDictionary),因为SignalR的Hub方法是并发调用的,普通Dictionary会引发线程安全问题; - 上述方案的数据都存储在内存中,当服务器重启或应用池回收时,数据会丢失。如果需要持久化连接关系,可结合Redis或数据库实现分布式存储。
内容的提问来源于stack exchange,提问作者FoxMaccloud
相关产品推荐
相关产品推荐

