ASP.NET MVC中如何识别当前视图,实现全页面SignalR消息通知?
解决SignalR根据用户当前页面选择调用不同客户端方法的问题
1. 客户端传递当前页面信息到SignalR Hub
在_Layout.cshtml的SignalR初始化逻辑里,把当前页面的路径(或自定义标识)传递给Hub,有两种实用方式:
方式一:连接时通过查询字符串携带
// _Layout.cshtml中的SignalR初始化脚本 const connection = new signalR.HubConnectionBuilder() .withUrl("/msgHub", { query: { currentPage: window.location.pathname // 示例值:"/Messages/Index" } }) .build(); connection.start().catch(err => console.error(err.toString()));
方式二:连接成功后主动调用Hub方法上报
connection.start().then(() => { // 连接建立后,调用Hub的SetCurrentPage方法传递当前页面 return connection.invoke("SetCurrentPage", window.location.pathname); }).catch(err => console.error(err.toString()));
2. 在MSGHub中维护用户页面状态
用线程安全的字典存储每个连接对应的用户ID和当前页面,确保多并发场景下的安全性:
using Microsoft.AspNetCore.SignalR; using System.Collections.Concurrent; using System.Security.Claims; public class MSGHub : Hub { // 并发安全字典:键=连接ID,值=(用户ID, 当前页面路径) private static readonly ConcurrentDictionary<string, (string UserId, string CurrentPage)> _userPageMap = new(); public override async Task OnConnectedAsync() { var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userId == null) { await base.OnConnectedAsync(); return; } // 对应方式一:从查询字符串获取当前页面 var currentPage = Context.GetHttpContext().Request.Query["currentPage"].ToString(); _userPageMap.TryAdd(Context.ConnectionId, (userId, currentPage)); await base.OnConnectedAsync(); } // 对应方式二:接收客户端上报的当前页面 public async Task SetCurrentPage(string currentPage) { var userId = Context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userId == null) return; _userPageMap.TryUpdate(Context.ConnectionId, (userId, currentPage), _userPageMap[Context.ConnectionId]); } public override async Task OnDisconnectedAsync(Exception exception) { _userPageMap.TryRemove(Context.ConnectionId, out _); await base.OnDisconnectedAsync(exception); } }
3. 发送消息时根据页面选择客户端方法
当需要推送新消息时,先从字典中筛选目标用户的所有活跃连接,再根据每个连接的当前页面调用对应方法:
public async Task SendNewMessage(string targetUserId, MessageDto message) { // 找到目标用户的所有在线连接 var userConnections = _userPageMap.Where(kv => kv.Value.UserId == targetUserId); foreach (var connection in userConnections) { var currentPage = connection.Value.CurrentPage; if (currentPage.Equals("/Messages/Index", StringComparison.OrdinalIgnoreCase)) { // 用户在消息页面,调用页面专属的ReceiveMessage方法 await Clients.Client(connection.Key).SendAsync("ReceiveMessage", message); } else { // 用户不在消息页面,调用菜单通知的NotifMessage方法 await Clients.Client(connection.Key).SendAsync("NotifMessage", message.UnreadCount); } } }
额外注意事项
- 多标签页处理:同一个用户打开多个标签页时,每个标签页对应独立连接,字典会存储多条记录,发送时需遍历所有连接分别处理。
- 页面切换更新:用户在同一标签页切换页面时,要在目标页面的脚本中调用
SetCurrentPage更新状态,比如在Messages/Index.cshtml中添加:connection.invoke("SetCurrentPage", window.location.pathname); - 并发安全:必须使用
ConcurrentDictionary存储用户页面映射,避免多线程冲突。
内容的提问来源于stack exchange,提问作者AMP59
相关产品推荐
相关产品推荐

