SignalR长连接在ABPVNext中应用,并结合实际业务服务使用
长轮询SignalR
了解SignalR
统计在线人数或发通信等功能,需要长轮询机制,最佳应用就是WebSocket,首先理解一下WebSocket:
- WebSocket基于TCP协议,支持二进制通信,双工通信。(两端可以互相发送)
- 性能和并发能力更强。
- WebSocket独立于HTTP协议,不过一般仍然把WebSocket服务器端部署到Web服务器上,因为可以借助HTTP协议完成初始的握手(可选),并且共享HTTP服务器的端口。
注意:webSocket 和 HTTP 是同一等级的,他们都是基于TCP协议的,HTTP是文本通讯,消耗比较高,WebSocekt是二进制,消耗低。
在ABP中,我们使用SignalR,SignalR可以理解成非常简单易用的高阶的API,使服务器端可以单个或批量调用客户端上的JavaScript函数,并且非常方便地进行连接管理,例如客户端连接到服务端,或断开连接,客户端分组等使用SignalR都非常容易实现。
l ASP.NET Core SignalR(以下简称SignalR),是.NET Core平台下对WebSocket的封装,可理解为一个WebSockets API,原理和WebSockets是一致的。
l Hub(集线器),数据交换中心。

说明:可以按照标准的微软教程添加SignalR到你的应用程序,但ABP提供了简化集成的SignalR集成包。
SignalR使用的其他场景,举个例子:
前端需要请求一个excel文件,这个excel文件是一个由后端动态生成的,前端传很多过滤条件,后端根据过滤条件,动态地生成数据,插入到excel表格,当数据量过大,上千或过万条数据HTTP请求一直在连接中,导致报了499错误,客户端会主动断开连接。
解决方案:使用后台作业比如HangFire或使用长轮询机制SignalR,它们都已经集成到了ABP框架中。
配置SignalR
首先,引入ABP中的SignalR包,如下图所示。

然后,在Module层进行如下配置,添加 AbpAspNetCoreSignalRModule 到你的模块的依赖列表,并配置SignalR的集线器Hub类型(继承AbpHub 基类),如下图所示:

集线器Hub
可以从 AbpHub 或 AbpHub<T> 继承标准的 Hub 和 Hub<T> 类,它们具有实用的基本属性,如 CurrentUser,示例代码如下图所示。

检查上述配置是否成功,打开项目之后,跳转到myhub(自己定义的路径)来检测,如果页面上出现Connection ID required就证明后端配置完全正确。

说明:Hub基于RPC(远程服务调用),接受从客户端发过来的消息,也同时负责把服务端的消息发送给客户端。客户端可以调用Hub里面的方法,服务端可以通过Hub调用客户端里面的方法。

还要说明Caller与其他客户端通信对象的区别,比如Clients.All是给所有客户端,Clients.Group是给某个组,而Caller仅针对当前调用者,确保用户理解其针对性。
总结时,要明确Caller的核心作用是实现与发起请求的客户端进行单独通信,保证消息或状态只传递给该客户端,适用于需要向请求方返回专属信息的场景,比如验证结果、个人状态更新等。

集成实际ABP业务
明确一点:SignalR 的 OnConnectedAsync 是生命周期方法,无法直接添加自定义参数(连接建立时自动触发,由 SignalR 框架调用),但可通过「连接时携带参数 + 后端解析」的间接方式传递参数,再在该方法中调用 ABP vNext 业务服务。
说明:依赖注入,Hub 继承 AbpHub 后,可直接通过构造函数注入 ABP 业务服务(如 IUserAppService),无需手动获取 IServiceProvider。
前端传参例子
如果调用的业务方法需要传参数,可通过以下两种方式:
- 推荐「查询字符串」:简单直观,前端拼接方便,后端解析无需额外配置。
- 备选「请求头」:适合敏感参数(如自定义令牌),前端需设置 withUrl 的 headers 参数,后端通过 Context.GetHttpContext()?.Request.Headers["X-Biz-Param"] 解析。

在ABPVNext中的HttpApiHostModule可进行接token认证参数配置,SignalR的接参不支持Header,可以在ABP的配置模块中进行如下配置,进行兼容操作,代码如下:
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); options.Audience = "BusinessService"; // 添加自定义Token获取逻辑 options.Events = new JwtBearerEvents { OnMessageReceived = context => { // 从URL参数获取token(优先处理SignalR场景) if (context.Request.Query.TryGetValue("access_token", out var queryToken)) { var token = queryToken.ToString(); // 清理可能的双引号(如前端误加的"") token = token.Trim('"'); // 清理可能的Bearer前缀(如前端误加的"Bearer ") if (token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { token = token.Substring("Bearer ".Length).Trim(); } context.Token = token; // 赋值清理后的token } return Task.CompletedTask; } }; });
其他配置如下:
#region SignalR配置 Configure<AbpSignalROptions>(options => { options.Hubs.AddOrUpdate( typeof(MessageHub), config => { config.RoutePattern = "/messageHub"; config.ConfigureActions.Add(hubOptions => { //Additional options hubOptions.LongPolling.PollTimeout = TimeSpan.FromSeconds(30); }); } ); }); context.Services.AddSignalR(options => { options.KeepAliveInterval = TimeSpan.FromSeconds(5);//心跳间隔 options.EnableDetailedErrors = true;//全局启用详细错误 }); #endregion
创建Hub类时,要设置一下其生命周期,如下图所示(设置成了单例模式),建议注册上ABP的生命周期使用Hub长寿一点,因为Hub对象本身的生命周期太短了。
Hub集线类还可以注册到业务服务或控制器中,可以对外进行暴露接口,示例代码如下(依赖注入方式一样使用):

创建SignalR客户端
示例代码如下(首先下载:npm install @aspnet/signalr):

上述代码中的accessTokenFactory回调用于获取access_token的,会在每次请求时调用以保证获取最新的access_token。
注意:WebSockets不支持自定义Header,所以不能使用Authorization,需要使用access_token参数传递令牌。
另外,核心原则:URL 参数access_token的值必须是纯 JWT 字符串(无引号、无Bearer 前缀),如下图所示。

回落机制:
为了兼容不同浏览器(客户端)和服务端,signalR采用了回落机制,使得它可以根据情况协商使用不同的底层传输方式。假如浏览器不支持web socket,就自动降级使用sse,再不行就long polling。当然,也可以禁用这种机制,指定其中一种。
三种通信方式:
l long polling(长轮询)
长轮询是客户端发起请求到服务端,服务器有数据就会直接返回。如果没有数据就保持连接并且等待,一直到有新的数据返回。如果请求保持到一段时间仍然没有返回,这时候就会超时,然后客户端再次发起请求。
这种方式优点就是简单,缺点就是资源消耗太多,基本是不考虑的。
l server sent events(sse)
如果使用了sse,服务器就拥有了向客户端推送的能力,这些信息和流信息差不多,期间会保持连接。
这种方式优点还是简单,也支持自动重连,综合来讲比long polling好用。缺点也很明显,不支持旧的浏览器不说,还只能发送本文信息,而且浏览器对sse还有连接数量的限制(6个)。
l web socket
web socket允许客户端和服务端同时向对方发送消息(也就是双工通信),而且不限制信息类型。虽然浏览器同样有连接数量限制(可能是50个),但比sse强得多。理论上最优先使用。
简单测试的实际运行效果,如下图所示。


另外,要会用前端引入的signalr.js中对signalr的操作,SignalR 的connection.on("onlineNum", ...)会监听服务端推送的onlineNum消息,回调参数data是服务端返回的 JSON 对象(已自动解析),注意:在调用 On 方法时,方法名称要与服务器上指定的一致,否则服务器回调无效!

还可以在前端直接调用在hub集线器类中定义的方法,

前端使用invoke来直接调用:
//测试直接调用hub中的方法 document.getElementById("sendButton").addEventListener("click", function (event) { // 无需传递param,因为后端方法无参数 var target = document.getElementById("requestInput").value; var param = document.getElementById("paramInput").value; if(param != ""){ connection.invoke(target, param).catch(function (err) { return console.error(err.toString()); }); } else{ connection.invoke(target) .then(count => { // 接收后端返回的在线人数 console.log("当前在线人数:", count); // 可在这里更新UI,例如显示count }) .catch(function (err) { return console.error(err.toString()); }); } event.preventDefault(); });
浙公网安备 33010602011771号