实时通信(Aegis.Net.SignalR)
当你需要在 Aegis Web API 中提供 Hub、服务端主动推送、跨节点实时广播能力时,可以接入 Aegis.Net.SignalR。本文按 Aegis 3.x / .NET 8 口径说明当前组件的真实接入方式,读完后你应该能完成组件引入、Hub 暴露、基础配置和服务端消息推送。
组件概览
| 字段 | 说明 |
|---|---|
| 组件名称 | 实时通信 |
| 真实类库 | Aegis.Net.SignalR |
| 组件定位 | 基于 SignalR 提供 Hub 注册、自动路由映射和 Redis 分布式连接能力 |
| 引入方式 | Component.deps.json |
| 组件声明 | SignalR |
| 核心能力 | 自动扫描 Hub / Hub<T>、自动映射路由、支持 IHubContext、支持 Redis Backplane |
| 是否可扩展 | 是 |
| 目标框架 | net8.0 |
| 注册入口 | src/Net/Aegis.Net.SignalR/ServiceCollectionExtensions.cs |
这个组件的边界比较明确:它负责把 SignalR 接入到 Aegis 的组件装配流程中,并自动完成 Hub 路由暴露。它不负责业务消息模型设计,也不替代前端或桌面端的客户端接入代码。如果你只需要单机内存模式的原生 SignalR,而不走 Aegis 组件装配,可以改用手动 AddSignalR() 的方式,这属于另一路接入方案。
如何引入
NuGet 包
先安装当前组件包:
| 角色 | NuGet 包 | 是否必需 | 说明 |
|---|---|---|---|
| 主组件 | Aegis.Net.SignalR | 是 | 提供服务注册、Hub 扫描和路由映射 |
Component.deps.json
当前组件既有服务注册逻辑,也有中间件加载逻辑,因此 SignalR 应同时出现在 Services 和 Middlewares 中:
{
"Components": {
"Services": [
"SignalR"
],
"Middlewares": [
"SignalR"
]
}
}
原因如下:
Services阶段会调用AddSignalR().AddStackExchangeRedis(...)完成服务注册。Middlewares阶段会扫描当前应用已加载程序集中的Hub类型,并自动映射到实际路由。
如果你的项目还启用了跨域和鉴权,推荐中间件顺序参考下面的结构:
{
"Components": {
"Middlewares": [
"Swagger",
"Cors",
"SignalR",
"Authentication",
"Authorization"
]
}
}
Cors 放在 SignalR 之前,能够减少浏览器预检请求和带凭据连接时的跨域问题。
配置说明
组件读取 SignalR 配置节点,对应的配置类型是 SignalROptions:
| 节点 | 类型 | 是否必填 | 说明 | 常见取值 / 示例 |
|---|---|---|---|---|
SignalR:DistributedConnection | string | 建议是 | Redis 连接串,服务注册阶段会把它传给 AddStackExchangeRedis(...) | 127.0.0.1:6379,defaultDatabase=2 |
SignalR:TransportType | string | 否 | 限制允许的传输方式;不配置时按 SignalR 默认行为处理 | WebSockets,ServerSentEvents,LongPolling |
最小配置示例:
{
"SignalR": {
"DistributedConnection": "127.0.0.1:6379,defaultDatabase=2",
"TransportType": "WebSockets,ServerSentEvents,LongPolling"
}
}
补充说明:
- 当前组件默认按 Redis Backplane 方式完成服务注册,因此生产接入时应准备可用的 Redis 连接串。
- 如果你只是做单机本地实验,又暂时不准备接入 Redis,更适合改用原生 SignalR 的手动注册方式。
快速使用
这一节给你一条最短路径,用来确认组件已经真正生效。
第一步:完成依赖和配置
确保你已经:
- 安装
Aegis.Net.SignalR - 在
Component.deps.json的Services和Middlewares中都加入SignalR - 在配置文件中加入
SignalR节点
第二步:新增一个 Hub
在 API 项目中新增一个 Hub 类,例如:
using Microsoft.AspNetCore.SignalR;
namespace Demo.Hubs;
public class NotificationHub : Hub
{
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
第三步:启动应用并确认路由
当前组件会自动扫描 Hub / Hub<T> 类型,并按类名生成路由:
NotificationHub->/notificationhubTestHub->/testhub
启动后,如果组件正常加载,你应该能在启动输出中看到类似 Endpoint 注册成功: /notificationhub 的信息,并且任意 SignalR 客户端都可以连接到这个地址。
具体使用详情
路由生成规则
当前组件不会要求你手写 app.MapHub<...>()。它会在中间件加载阶段自动扫描当前应用已加载程序集中的 Hub 类型,并按下面的规则暴露路由:
- 如果 Hub 类上标记了
HubNameAttribute,优先使用该名称。 - 否则使用类名。
- 如果名称以
Hub结尾,会先移除这个后缀。 - 最终统一转成小写,并拼上
hub后缀。
例如:
using Aegis.Net.SignalR;
using Microsoft.AspNetCore.SignalR;
[HubName("queue")]
public class QueueHub : Hub
{
}
上面的 Hub 最终会映射为 /queuehub。
在 Hub 内部发送消息
如果消息只在 Hub 调用链内完成,可以直接使用 Clients:
using Microsoft.AspNetCore.SignalR;
namespace Demo.Hubs;
public class TestHub : Hub
{
public async Task GetCurrentNumber()
{
var number = Random.Shared.Next(0, 100);
await Clients.All.SendAsync("ReceiveCurrentNumber", number);
await Clients.All.SendAsync("UpdateQueueInfo", number - 1, number);
}
}
常见发送目标包括:
Clients.All:广播给所有连接Clients.Caller:只返回给当前调用方Clients.Others:广播给其他连接,不包含当前调用方
在 Hub 外部主动推送
如果你需要在 Controller、应用服务或后台任务里主动推送消息,可以注入 IHubContext<T>:
using Demo.Hubs;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
namespace Demo.Controllers;
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
private readonly IHubContext<TestHub> _hubContext;
public TestController(IHubContext<TestHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet("send")]
public async Task Send()
{
await _hubContext.Clients.All.SendAsync("SSETest", "1");
}
}
这种方式适合:
- 业务接口触发通知
- 定时任务推送实时状态
- 审批、告警、排队叫号等服务端主动广播场景
连接生命周期处理
如果你需要在客户端连接和断开时执行逻辑,可以重写 OnConnectedAsync 和 OnDisconnectedAsync:
using Microsoft.AspNetCore.SignalR;
namespace Demo.Hubs;
public class TestHub : Hub
{
public override Task OnConnectedAsync()
{
Console.WriteLine($"{Context.ConnectionId}-建立连接");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception? exception)
{
Console.WriteLine($"{Context.ConnectionId}-断开连接");
return base.OnDisconnectedAsync(exception);
}
}
适合放在这里的逻辑通常包括在线状态更新、连接审计和简单会话初始化。涉及复杂鉴权、房间管理或在线用户表时,建议再配合业务服务层处理,不要把全部逻辑堆在 Hub 本身。
如何扩展
当前组件至少有三类常见扩展方式。
自定义 Hub 路由名
使用 HubNameAttribute 可以避免直接暴露类名,适合对外路由需要稳定命名时使用:
using Aegis.Net.SignalR;
using Microsoft.AspNetCore.SignalR;
[HubName("notice")]
public class NotificationHub : Hub
{
}
这样最终路由会变成 /noticehub,而不是按类名推导。
使用强类型 Hub
如果你不希望在 SendAsync("方法名") 里手写字符串,可以改用强类型 Hub:
using Microsoft.AspNetCore.SignalR;
public interface IChatClient
{
Task ReceiveMessage(string message);
}
public class ChatHub : Hub<IChatClient>
{
public async Task SendMessage(string message)
{
await Clients.All.ReceiveMessage(message);
}
}
这种写法更适合长期维护,可以减少方法名拼写错误,也更利于重构。
与 Redis 组合做多节点广播
当前组件的服务注册阶段会把 SignalR:DistributedConnection 传给 AddStackExchangeRedis(...),因此它天然适合部署到多实例场景。常见用途包括:
- 多节点 Web API 的统一广播
- 多台应用服务器之间共享连接状态
- 将实时通知能力接入容器或负载均衡环境
如果你准备走这条路线,建议把 Redis 连通性和跨域策略一起纳入部署检查项。
常见问题指南
为什么只把 SignalR 放进 Middlewares 后,IHubContext 仍然不可用?
因为当前组件把服务注册和中间件加载分成了两个阶段:
RegisterService(...)负责AddSignalR().AddStackExchangeRedis(...)RegisterMiddleware(...)负责自动扫描并映射 Hub 路由
只写 Middlewares 会导致路由加载阶段能执行,但服务注册阶段不会执行。为了让组件完整生效,建议在 Services 和 Middlewares 中都加入 SignalR。
为什么我写了 Hub,但访问不到对应地址?
优先检查下面几项:
- Hub 类是否继承自
Hub或Hub<T> SignalR是否已经加入Middlewares- Hub 所在程序集是否已被应用加载
- 实际访问路径是否符合
/{hub名}hub规则 - 是否用了
HubNameAttribute改写名称
为什么浏览器连接时会遇到跨域或鉴权问题?
跨域场景下,SignalR 通常要和 Cors、Authentication 一起看顺序。当前更稳妥的做法是:
- 先加载
Cors - 再加载
SignalR - 再加载
Authentication/Authorization
如果你的前端需要带凭据连接,不要只看普通 API 的 CORS 配置,也要一起检查 Hub 连接链路。
不想依赖 Redis,可以直接用这个组件吗?
当前组件在服务注册阶段默认走 Redis Backplane 方案。如果你只是做单机、本地或临时实验,而又不想准备 Redis,更适合改走手动 builder.Services.AddSignalR() 和 app.MapHub<...>() 的原生接入方式。
下一步看什么
- 想了解旧版主题说明和基础用法,可以参考 SignalR
- 如果项目同时启用了跨域和认证,建议一并核对
Aegis.Core.Cors、Aegis.Core.Authentication、Aegis.Core.Authorization的中间件顺序