如何利用SignalR检测浏览器关闭事件?MVC Razor页面场景
用SignalR实现MVC Razor页面的用户连接/断开事件检测
问题背景
试过多种jQuery、JavaScript方法检测浏览器关闭事件,但onbeforeunload和onunload方法均无效。需要在MVC Razor视图页面中,精准触发用户初始连接事件,以及用户断开页面的事件(包括浏览器关闭、意外关机等场景),如何通过SignalR实现该需求?
已尝试的代码:
window.onbeforeunload = function (event) { var message = 'Important: Please click on \'Save\' button to leave this page.'; if (typeof event == 'undefined') { event = window.event; } if (event) { event.returnValue = message; } return message; }; $(function () { $("a").not('#lnkLogOut').click(function () { window.onbeforeunload = null; }); $(" .btn").click(function () { window.onbeforeunload = null; }); });
解决方案
一、后端配置SignalR服务
安装SignalR包
在项目中通过NuGet安装Microsoft.AspNetCore.SignalR包。配置SignalR中间件
在Startup.cs(.NET Core)中注册SignalR服务:
public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); // 其他服务配置... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 其他中间件配置... app.UseEndpoints(endpoints => { endpoints.MapHub<UserTrackHub>("/userTrackHub"); // 其他端点配置... }); }
- 创建用户追踪Hub类
新建Hub类处理连接与断开事件,记录用户连接状态:
using Microsoft.AspNetCore.SignalR; using System.Collections.Concurrent; public class UserTrackHub : Hub { // 存储用户ID与对应连接ID集合 private static readonly ConcurrentDictionary<string, HashSet<string>> _userConnections = new ConcurrentDictionary<string, HashSet<string>>(); public override async Task OnConnectedAsync() { var userId = Context.User.Identity.Name; var connectionId = Context.ConnectionId; // 添加用户连接记录 _userConnections.AddOrUpdate(userId, new HashSet<string> { connectionId }, (key, existing) => { existing.Add(connectionId); return existing; }); // 触发初始连接事件,比如记录日志、推送在线状态 await Clients.All.SendAsync("UserConnected", userId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { var userId = Context.User.Identity.Name; var connectionId = Context.ConnectionId; // 移除对应连接记录 if (_userConnections.TryGetValue(userId, out var connections)) { connections.Remove(connectionId); if (connections.Count == 0) { _userConnections.TryRemove(userId, out _); // 用户完全离线时触发断开事件 await Clients.All.SendAsync("UserDisconnected", userId); } } await base.OnDisconnectedAsync(exception); } }
二、前端Razor页面集成SignalR
在Razor视图中引入SignalR脚本并初始化连接:
<!-- 引入SignalR脚本 --> <script src="~/lib/signalr/dist/browser/signalr.min.js"></script> <script> // 初始化SignalR连接 const connection = new signalR.HubConnectionBuilder() .withUrl("/userTrackHub") .configureLogging(signalR.LogLevel.Information) .build(); // 监听服务端推送的连接事件 connection.on("UserConnected", function (userId) { console.log(`用户 ${userId} 已上线`); // 可在此更新前端在线用户列表等 }); // 监听服务端推送的断开事件 connection.on("UserDisconnected", function (userId) { console.log(`用户 ${userId} 已下线`); // 处理前端离线逻辑 }); // 启动连接,失败自动重试 async function start() { try { await connection.start(); console.log("SignalR连接建立成功"); // 前端初始连接后的自定义逻辑 } catch (err) { console.error(err); setTimeout(start, 5000); } } // 页面加载时启动连接 start(); // 无需依赖onbeforeunload,SignalR会自动检测所有断开场景 </script>
关键说明
替代onbeforeunload的原因
现代浏览器对onbeforeunload/onunload有严格限制:禁止自定义提示文本,意外关机、浏览器崩溃等场景无法触发,无法精准感知断开。而SignalR基于WebSocket/长轮询机制,服务端主动检测连接状态,连接中断时自动触发OnDisconnectedAsync,覆盖所有断开场景。多标签页处理
Hub类中用HashSet存储同一用户的多个连接ID,确保只有用户关闭所有标签页时,才会触发完全离线事件。
内容的提问来源于stack exchange,提问作者Behnam Faghih
相关产品推荐
相关产品推荐

