ASP.NET Core SignalR 的工作原理
服务器和 Hub
类
Hub
类是 SignalR 服务器的概念。 它在 Microsoft.AspNetCore.SignalR
命名空间中定义,并且是 Microsoft.AspNetCore.SignalR NuGet 包的一部分。 面向 Microsoft.NET.Sdk.Web SDK 的 ASP.NET Core Web 应用无需添加 SignalR 的包引用,因为它已作为共享框架的一部分提供。
Hub
通过路由公开。 例如,https://www.contoso-pizza.com/hubs/orders
路由可用于表示 OrdersHub
实现。 通过各种中心 API,作者可以定义方法和事件。
有两个方式可以在中心上公开方法。 创建以下类型的子类和编写方法:
示例 Hub
作为参考点,请考虑以下 Notification
对象:
namespace RealTime.Models;
public record Notification(string Text, DateTime Date);
可以在使用 .NET 客户端 SDK 时共享该对象,使服务器和客户端具有完全相同的对象。 假设有一个如下所述的通知中心:
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;
using RealTime.Models;
namespace ExampleServer.Hubs;
public sealed class NotificationHub : Hub
{
public Task NotifyAll(Notification notification) =>
Clients.All.SendAsync("NotificationReceived", notification);
}
对于方法与事件之间的差异,上述中心实施中的方法是 NotifyAll
,而事件是 NotificationReceived
。 NotificationHub
必须是 Hub
的子类。 NotifyAll
方法返回 Task
,并接受单个 Notification
参数。 该方法表示为从 Clients.All
调用 SendAsync
,Clients.All
表示所有连接的客户端。 将激发 NotificationReceived
事件,依赖于 notification
实例。
IHubContext
实例
从 Hub
或 IHubContext
实例激发事件。 SignalR 中心是用于将消息发送到连接到 SignalR 服务器的客户端的核心抽象。 还可以使用以下任一类型从应用中的其他位置发送消息:
- IHubContext<THub>:一个上下文,其中
THub
表示标准中心。 - IHubContext<THub,T>:一个上下文,其中
THub
表示强类型泛型中心,T
表示相应类型的客户端。
重要
IHubContext
用于向客户端发送通知。 它不用于调用 Hub
上的方法。
IHubContext
示例
考虑到上一个通知中心实施,你可以使用 IHubContext<NotificationHub>
,如下所示:
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks;
using RealTime.Models;
namespace ExampleServer.Services;
public sealed class NotificationService(
IHubContext<NotificationHub> hubContext)
{
public Task SendNotificationAsync(Notification notification) =>
notification is not null
? hubContext.Clients.All.SendAsync("NotificationReceived", notification)
: Task.CompletedTask;
}
前面的 C# 代码依赖 IHubContext<NotificationHub>
来访问客户端的上下文列表,公开了广播通知的功能。 在作用域中捕获的 hubContext
主构造函数参数用于触发 "NotificationReceived"
事件,但它并不旨在用于调用中心的 NotifyAll
方法。
方法
Hub
或 Hub<T>
方法与其他任何 C# 方法相同。 它们都定义返回类型、方法名称和参数。
- Hub 方法最常见的返回类型为
Task
或Task<TResult>
,后者表示异步 hub 操作。 - 方法名称用于从客户端调用方法。 你可以使用 HubMethodNameAttribute 对其进行自定义。
- 参数是可选的,但在定义时,客户端应提供相应的参数。
方法不需要触发事件,但通常会触发。
事件
可以从客户端按名称来订阅事件。 服务器负责引发事件。 Hub
、Hub<T>
、IHubContext<THub>
和 IHubContext<THub, T>
事件已命名,并且最多可以定义 10 个参数。 事件在服务器上触发,并由感兴趣的客户端进行处理。 当客户端订阅其 hub 连接上的事件时,该客户端则视为感兴趣的客户端。 当客户端调用由于其调用而激发事件的中心方法时,会间接地触发事件。 不过,客户端无法直接触发事件,因为这是服务器的职责。
事件客户端范围
从 IClientProxy 实例调用事件。 你从 Clients 类型实现 IHubClients和 IHubCallerClients 接口。 有多种方法可以将范围界定到特定 IClientProxy
实例。 可以从 Hub.Clients
属性定位以下范围:
成员 | 详细信息 |
---|---|
All |
所有连接的客户端(例如广播)。 |
AllExcept |
所有连接的客户端,不包括指定的连接(例如筛选的广播)。 |
Caller |
触发方法的连接的客户端(如回显)。 |
Client |
指定的客户端连接(单个连接)。 |
Clients |
指定的客户端连接(多个连接)。 |
Group |
指定组中的所有连接的客户端。 |
GroupExcept |
指定组中的所有连接的客户端(不包括指定的连接)。 |
Groups |
指定组中的所有连接的客户端(多个组)。 |
Others |
所有连接的客户端(不包括触发方法的客户端)。 |
OthersInGroup |
指定组中的所有连接的客户端(不包括触发方法的客户端)。 |
User |
指定用户的所有连接的客户端(单个用户可以连接到多个设备)。 |
Users |
指定用户的所有连接的客户端。 |
示例范围
请考虑以下图片,这些图片直观地显示了中心是如何向目标客户端发送消息的。 你可以展开图片,以方便阅读。
客户端和 HubConnection
类
HubConnection
类是 SignalR 客户端概念,表示客户端与服务器 Hub
的连接。 它在 Microsoft.AspNetCore.SignalR.Client
命名空间中定义,并且是 Microsoft.AspNetCore.SignalR.Client NuGet 包的一部分。
你使用生成器模式和相应的 HubConnectionBuilder
类型创建 HubConnection
。 考虑到中心的路由(或者 System.Uri),你可以创建 HubConnection
。 生成器还可以指定其他配置选项,包括日志记录、所需的协议、身份验证令牌转发和自动重新连接,等等。
HubConnection
API 公开启动和停止函数,分别用于启动和停止到服务器的连接。 此外,还提供了流式处理、调用 hub 方法和订阅事件的功能。
创建 HubConnection
示例
若要从 .NET SignalR 客户端 SDK 创建 HubConnection
对象,请使用 HubConnectionBuilder
类型:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Threading.Tasks;
using RealTime.Models;
namespace ExampleClient;
public sealed class Consumer : IAsyncDisposable
{
private readonly string HostDomain =
Environment.GetEnvironmentVariable("HOST_DOMAIN");
private HubConnection _hubConnection;
public Consumer()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl(new Uri($"{HostDomain}/hub/notifications"))
.WithAutomaticReconnect()
.Build();
}
public Task StartNotificationConnectionAsync() =>
_hubConnection.StartAsync();
public async ValueTask DisposeAsync()
{
if (_hubConnection is not null)
{
await _hubConnection.DisposeAsync();
_hubConnection = null;
}
}
}
调用 hub 方法
如果客户端已经有一个已成功启动的客户端 HubConnection
实例,则该客户端可以使用 InvokeAsync 或 SendAsync 扩展调用中心上的方法。 如果中心方法返回 Task<TResult>
,则 InvokeAsync<TResult>
结果的类型为 TResult
。 如果中心方法返回 Task
,则不会生成任何结果。 InvokeAsync
和 SendAsync
都需要中心方法的名称,以及 0 到 10 个参数。
- InvokeAsync:使用指定的方法名称和可选参数调用服务器上的中心方法。
- SendAsync:使用指定的方法名称和可选参数调用服务器上的中心方法。 此方法不会等待接收方的响应。
中心方法调用示例
SendNotificationAsync
向之前的 Consumer
类添加方法时,SendNotificationAsync
将委托给 _hubConnection
,并根据 Notification
实例调用服务器中心上的 NotifyAll
方法。
public Task SendNotificationAsync(string text) =>
_hubConnection.InvokeAsync(
"NotifyAll", new Notification(text, DateTime.UtcNow));
处理事件
若要处理事件,请在 HubConnection
实例上注册一个处理程序。 如果你知道中心方法的名称并拥有 0 到 8 个参数,请调用其中一个 HubConnectionExtensions.On 重载。 处理程序可以满足以下任何 Action
变体:
- Action
- Action<T>
- Action<T1,T2>
- Action<T1,T2,T3>
- Action<T1,T2,T3,T4>
- Action<T1,T2,T3,T4,T5>
- Action<T1,T2,T3,T4,T5,T6>
- Action<T1,T2,T3,T4,T5,T6,T7>
- Action<T1,T2,T3,T4,T5,T6,T7,T8>
或者,可以使用异步处理程序 API,当 TResult
是 Task
变体时,它们是 Func<TResult>
:
Func<Task>
Func<T,Task>
Func<T1,T2,Task>
Func<T1,T2,T3,Task>
Func<T1,T2,T3,T4,Task>
Func<T1,T2,T3,T4,T5,Task>
Func<T1,T2,T3,T4,T5,T6,Task>
Func<T1,T2,T3,T4,T5,T6,T7,Task>
Func<T1,T2,T3,T4,T5,T6,T7,T8,Task>
注册事件处理程序的结果是 IDisposable
,它充当订阅。 若要取消订阅处理程序,请调用 Dispose。
事件注册示例
更新上一个 Consumer
类时,通过提供处理程序并调用 On
来注册事件:
using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Threading.Tasks;
using RealTime.Models;
namespace ExampleClient;
public sealed class Consumer : IAsyncDisposable
{
private readonly string HostDomain =
Environment.GetEnvironmentVariable("HOST_DOMAIN");
private HubConnection _hubConnection;
public Consumer()
{
_hubConnection = new HubConnectionBuilder()
.WithUrl(new Uri($"{HostDomain}/hub/notifications"))
.WithAutomaticReconnect()
.Build();
_hubConnection.On<Notification>(
"NotificationReceived", OnNotificationReceivedAsync);
}
private async Task OnNotificationReceivedAsync(Notification notification)
{
// Do something meaningful with the notification.
await Task.CompletedTask;
}
// Omitted for brevity.
}
当服务器的 hub 实例激发 "NotificationReceived"
事件时,将调用 OnNotificationReceivedAsync
方法。
Contoso Pizza 实时订单更新
Web 应用的服务器代码需要具有 Hub
实施,并且向客户端公开路由。 Hub
可以使用订单对象的唯一标识符创建用于跟踪的组。 然后,可以在此组中传达所有订单状态更改更新。
还需要更新客户端代码,以指示 Contoso Pizza 应用程序是 Blazor WebAssembly 应用。 可以使用 JavaScript SDK 或 .NET 客户端 SDK。 然后,将客户端轮询功能替换为生成 HubConnection
的代码,并启动到服务器的连接。 当导航到订单跟踪页时,代码必须加入订单的特定组,更改更新将发送到该组。 你需要订阅事件以获取订单状态更改,并进行相应的处理。