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:标准中心。
  • Hub<T>:强类型泛型中心。

示例 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,而事件是 NotificationReceivedNotificationHub 必须是 Hub 的子类。 NotifyAll 方法返回 Task,并接受单个 Notification 参数。 该方法表示为从 Clients.All 调用 SendAsyncClients.All 表示所有连接的客户端。 将激发 NotificationReceived 事件,依赖于 notification 实例。

IHubContext 实例

HubIHubContext 实例激发事件。 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 方法。

方法

HubHub<T> 方法与其他任何 C# 方法相同。 它们都定义返回类型、方法名称和参数。

  • Hub 方法最常见的返回类型为 TaskTask<TResult>,后者表示异步 hub 操作。
  • 方法名称用于从客户端调用方法。 你可以使用 HubMethodNameAttribute 对其进行自定义。
  • 参数是可选的,但在定义时,客户端应提供相应的参数。

方法不需要触发事件,但通常会触发。

事件

可以从客户端按名称来订阅事件。 服务器负责引发事件。 HubHub<T>IHubContext<THub>IHubContext<THub, T> 事件已命名,并且最多可以定义 10 个参数。 事件在服务器上触发,并由感兴趣的客户端进行处理。 当客户端订阅其 hub 连接上的事件时,该客户端则视为感兴趣的客户端。 当客户端调用由于其调用而激发事件的中心方法时,会间接地触发事件。 不过,客户端无法直接触发事件,因为这是服务器的职责。

事件客户端范围

IClientProxy 实例调用事件。 你从 Clients 类型实现 IHubClientsIHubCallerClients 接口。 有多种方法可以将范围界定到特定 IClientProxy 实例。 可以从 Hub.Clients 属性定位以下范围:

成员 详细信息
All 所有连接的客户端(例如广播)。
AllExcept 所有连接的客户端,不包括指定的连接(例如筛选的广播)。
Caller 触发方法的连接的客户端(如回显)。
Client 指定的客户端连接(单个连接)。
Clients 指定的客户端连接(多个连接)。
Group 指定组中的所有连接的客户端。
GroupExcept 指定组中的所有连接的客户端(不包括指定的连接)。
Groups 指定组中的所有连接的客户端(多个组)。
Others 所有连接的客户端(不包括触发方法的客户端)。
OthersInGroup 指定组中的所有连接的客户端(不包括触发方法的客户端)。
User 指定用户的所有连接的客户端(单个用户可以连接到多个设备)。
Users 指定用户的所有连接的客户端。

示例范围

请考虑以下图片,这些图片直观地显示了中心是如何向目标客户端发送消息的。 你可以展开图片,以方便阅读。

  • 广播到所有对象

    ASP.NET Core SignalR hub sending message with Clients.All syntax.

    所有连接的客户端都将收到此消息,而不考虑它们可能属于或不属于的组。

  • 独立用户

    ASP.NET Core SignalR hub sending message with Clients.User syntax.

    单个用户会将收到此消息,无论当前正在使用多少设备。

  • 独立组

    ASP.NET Core SignalR hub sending message with Clients.Group syntax.

    只有属于特定组的客户端才会收到此消息。

客户端和 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 实例,则该客户端可以使用 InvokeAsyncSendAsync 扩展调用中心上的方法。 如果中心方法返回 Task<TResult>,则 InvokeAsync<TResult> 结果的类型为 TResult。 如果中心方法返回 Task,则不会生成任何结果。 InvokeAsyncSendAsync 都需要中心方法的名称,以及 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 变体:

或者,可以使用异步处理程序 API,当 TResultTask 变体时,它们是 Func<TResult>

注册事件处理程序的结果是 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 的代码,并启动到服务器的连接。 当导航到订单跟踪页时,代码必须加入订单的特定组,更改更新将发送到该组。 你需要订阅事件以获取订单状态更改,并进行相应的处理。