跳到主要内容
版本:3.0.0

实时通信(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 应同时出现在 ServicesMiddlewares 中:

{
"Components": {
"Services": [
"SignalR"
],
"Middlewares": [
"SignalR"
]
}
}

原因如下:

  • Services 阶段会调用 AddSignalR().AddStackExchangeRedis(...) 完成服务注册。
  • Middlewares 阶段会扫描当前应用已加载程序集中的 Hub 类型,并自动映射到实际路由。

如果你的项目还启用了跨域和鉴权,推荐中间件顺序参考下面的结构:

{
"Components": {
"Middlewares": [
"Swagger",
"Cors",
"SignalR",
"Authentication",
"Authorization"
]
}
}

Cors 放在 SignalR 之前,能够减少浏览器预检请求和带凭据连接时的跨域问题。

配置说明

组件读取 SignalR 配置节点,对应的配置类型是 SignalROptions

节点类型是否必填说明常见取值 / 示例
SignalR:DistributedConnectionstring建议是Redis 连接串,服务注册阶段会把它传给 AddStackExchangeRedis(...)127.0.0.1:6379,defaultDatabase=2
SignalR:TransportTypestring限制允许的传输方式;不配置时按 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.jsonServicesMiddlewares 中都加入 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 -> /notificationhub
  • TestHub -> /testhub

启动后,如果组件正常加载,你应该能在启动输出中看到类似 Endpoint 注册成功: /notificationhub 的信息,并且任意 SignalR 客户端都可以连接到这个地址。

具体使用详情

路由生成规则

当前组件不会要求你手写 app.MapHub<...>()。它会在中间件加载阶段自动扫描当前应用已加载程序集中的 Hub 类型,并按下面的规则暴露路由:

  1. 如果 Hub 类上标记了 HubNameAttribute,优先使用该名称。
  2. 否则使用类名。
  3. 如果名称以 Hub 结尾,会先移除这个后缀。
  4. 最终统一转成小写,并拼上 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");
}
}

这种方式适合:

  • 业务接口触发通知
  • 定时任务推送实时状态
  • 审批、告警、排队叫号等服务端主动广播场景

连接生命周期处理

如果你需要在客户端连接和断开时执行逻辑,可以重写 OnConnectedAsyncOnDisconnectedAsync

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 会导致路由加载阶段能执行,但服务注册阶段不会执行。为了让组件完整生效,建议在 ServicesMiddlewares 中都加入 SignalR

为什么我写了 Hub,但访问不到对应地址?

优先检查下面几项:

  • Hub 类是否继承自 HubHub<T>
  • SignalR 是否已经加入 Middlewares
  • Hub 所在程序集是否已被应用加载
  • 实际访问路径是否符合 /{hub名}hub 规则
  • 是否用了 HubNameAttribute 改写名称

为什么浏览器连接时会遇到跨域或鉴权问题?

跨域场景下,SignalR 通常要和 CorsAuthentication 一起看顺序。当前更稳妥的做法是:

  • 先加载 Cors
  • 再加载 SignalR
  • 再加载 Authentication / Authorization

如果你的前端需要带凭据连接,不要只看普通 API 的 CORS 配置,也要一起检查 Hub 连接链路。

不想依赖 Redis,可以直接用这个组件吗?

当前组件在服务注册阶段默认走 Redis Backplane 方案。如果你只是做单机、本地或临时实验,而又不想准备 Redis,更适合改走手动 builder.Services.AddSignalR()app.MapHub<...>() 的原生接入方式。

下一步看什么

  • 想了解旧版主题说明和基础用法,可以参考 SignalR
  • 如果项目同时启用了跨域和认证,建议一并核对 Aegis.Core.CorsAegis.Core.AuthenticationAegis.Core.Authorization 的中间件顺序