ASP.NET Core SignalR 的工作原理
服务器和 Hub
类
Hub
类是 SignalR 服务器的概念。 它在命名空间中 Microsoft.AspNetCore.SignalR
定义,是 Microsoft.AspNetCore.SignalR NuGet 包的一部分。 ASP.NET 面向 Microsoft.NET.Sdk.Web SDK 的核心 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
参数。 该方法表示为从 SendAsync
调用 Clients.All
,SendAsync
表示所有连接的客户端。 将激发 NotificationReceived
事件,依赖于 notification
实例。
IHubContext
实例
你可以从Hub
或IHubContext
实例触发事件。 SignalR 中心是用于将消息发送到连接到 SignalR 服务器的客户端的核心抽象。 还可以使用以下任一类型从应用中的其他位置发送消息:
- IHubContext<THub>:一个上下文,其中
THub
表示标准中心。 - IHubContext<THub,T>:一个上下文,其中
THub
表示强类型泛型中心,T
表示相应类型的客户端。
重要
IHubContext
用于向客户端发送通知。 它不用于调用 上的方法。
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 实例调用事件。 你从 IHubClients 类型实现 IHubCallerClients和 Clients 接口。 有多种方法可以将范围界定到特定 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 包的一部分。
你使用生成器模式和相应的 HubConnection
类型创建 HubConnectionBuilder
。 考虑到中心的路由(或者 System.Uri),你可以创建 HubConnection
。 生成器还可以指定其他配置选项,包括日志记录、所需的协议、身份验证令牌转发和自动重新连接,等等。
HubConnection
API 公开启动和停止函数,分别用于启动和停止到服务器的连接。 此外,还有一些功能可用于流式处理、调用 中心方法和订阅 事件。
创建 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
,并根据 NotifyAll
实例调用服务器中心上的 Notification
方法。
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,当 Func<TResult>
是 TResult
变体时,它们是 Task
:
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 实例激发 OnNotificationReceivedAsync
事件时,将调用 "NotificationReceived"
方法。
Contoso Pizza 实时订单更新
Web 应用的服务器代码需要具有 Hub
实施,并且向客户端公开路由。 Hub
可以使用订单对象的唯一标识符创建用于跟踪的组。 然后,可以在此组中传达所有订单状态更改更新。
还需要更新客户端代码,以指示 Contoso Pizza 应用程序是 Blazor WebAssembly 应用。 可以使用 JavaScript SDK 或 .NET 客户端 SDK。 然后,将客户端轮询功能替换为生成 HubConnection
的代码,并启动到服务器的连接。 当导航到订单跟踪页时,代码必须加入订单的特定组,更改更新将发送到该组。 你需要订阅事件以获取订单状态更改,并进行相应的处理。