Como funciona o SignalR do ASP.NET Core

Concluído

Servidores e a classe Hub

A classe Hub é um conceito de servidor SignalR. Ela é definida no namespace Microsoft.AspNetCore.SignalR e faz parte do pacote de NuGet Microsoft.AspNetCore.SignalR. Os aplicativos web do ASP.NET Core destinados ao SDK Microsoft.NET.Sdk.Web não precisam adicionar uma referência de pacote para o SignalR, porque esta já está disponível como parte da estrutura compartilhada.

Um Hub é exposto por meio de uma rota. Por exemplo, a rota https://www.contoso-pizza.com/hubs/orders pode ser usada para representar uma implementação OrdersHub. Com as várias APIs do hub, os autores podem definir métodos e eventos.

Há dois modos para expor métodos em um hub. Você cria uma subclasse dos seguintes tipos e métodos de gravação:

  • Hub: um hub padrão.
  • Hub<T>: um hub genérico fortemente tipado.

Exemplo Hub

Como ponto de referência, considere o seguinte objeto Notification:

namespace RealTime.Models;

public record Notification(string Text, DateTime Date);

O objeto pode ser compartilhado ao usar o SDK do cliente .NET, de modo que o servidor e o cliente tenham exatamente o mesmo objeto. Imagine um hub de notificações:

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);
}

No que se refere à diferença entre métodos e eventos, o método na implementação anterior do hub é NotifyAll e o evento é NotificationReceived. O NotificationHub é uma subclasse de Hub. O método NotifyAll retorna um Task e aceita um único parâmetro Notification. O método é expresso como a invocação para SendAsync de Clients.All, que representa todos os clientes conectados. O evento NotificationReceived é acionado dependendo da instância notification.

A instância IHubContext

Você dispara os eventos de uma instância de Hub ou IHubContext. O hub do SignalR é a abstração principal para enviar mensagens a clientes conectados ao servidor SignalR. Também é possível enviar mensagens de outros locais em seu aplicativo usando um dos seguintes tipos:

  • IHubContext<THub>: um contexto em que THub representa um hub padrão.
  • IHubContext<THub,T>: um contexto em que THub representa o hub genérico fortemente tipado e T representa o tipo de cliente correspondente.

Importante

IHubContext é para enviar notificações aos clientes. Ele não é usado para chamar métodos no Hub.

Por exemplo, IHubContext

No que diz respeito à implementação anterior do hub de notificações, você poderia usar o IHubContext<NotificationHub> da seguinte forma:

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;
}

O código C# anterior depende do IHubContext<NotificationHub> para acessar a listagem contextual de clientes, expondo a capacidade de difundir notificações. O parâmetro do construtor primário hubContext capturado no escopo é usado para disparar o evento "NotificationReceived", mas não se destina a ser usado para chamar o método NotifyAll do hub.

Métodos

Os métodos Hub ou Hub<T> são semelhantes a qualquer outro método C#. Eles definem um tipo de retorno, nome do método e parâmetros.

  • O tipo retornado mais comum para um método de hub é Task ou Task<TResult>, que representa a operação de hub assíncrona.
  • O nome do método é usado para chamar o método de clientes. Você pode personalizá-lo usando HubMethodNameAttribute.
  • Os parâmetros são opcionais, mas, quando definidos, é esperado que os clientes forneçam argumentos correspondentes.

Métodos não precisam disparar eventos, mas muitas vezes o fazem.

Eventos

É possível assinar um evento pelo nome de um cliente. O servidor é responsável por gerar eventos. Eventos Hub, Hub<T>, IHubContext<THub> e IHubContext<THub, T> são nomeados e podem definir até 10 parâmetros. Os eventos são acionados no servidor e manipulados por clientes interessados. Um cliente é considerado interessado quando assina eventos na conexão do hub. Os clientes poderão disparar eventos indiretamente ao chamar métodos de hub que acionam eventos como resultado de sua invocação. No entanto, os eventos não podem ser acionados diretamente pelos clientes, pois essa é a responsabilidade do servidor.

Escopos do cliente de evento

Você chama eventos de uma instância IClientProxy. Implemente as interfaces IHubClients e IHubCallerClients do tipo Clients. Há muitas maneiras de escopo para uma instância IClientProxy específica. Você pode direcionar os seguintes escopos da propriedade Hub.Clients:

Membro Detalhes
All Todos os clientes conectados (como uma difusão).
AllExcept Todos os clientes conectados, excluindo as conexões especificadas (como uma transmissão filtrada).
Caller O cliente conectado que disparou o método (como um eco).
Client A conexão de cliente especificada (conexão única).
Clients As conexões de cliente especificadas (várias conexões).
Group Todos os clientes conectados dentro do grupo especificado.
GroupExcept Todos os clientes conectados dentro do grupo especificado, exceto as conexões especificadas.
Groups Todos os clientes conectados dentro dos grupos especificados (vários grupos).
Others Todos os clientes conectados, exceto o cliente que disparou o método.
OthersInGroup Todos os clientes conectados dentro do grupo especificado, exceto o cliente que disparou o método.
User Todos os clientes conectados para o usuário especificado (um só usuário pode se conectar em mais de um dispositivo).
Users Todos os clientes conectados para os usuários especificados.

Escopos de exemplo

Considere as imagens a seguir, que podem ajudá-lo a visualizar como o hub envia mensagens para clientes de alvo. Você pode expandir as imagens para maior capacidade de leitura.

  • Transmitir para todos

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

    Todos os clientes conectados recebem essa mensagem, independentemente do grupo ao qual eles podem ou não pertencer.

  • Usuário isolado

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

    Um único usuário recebe essa mensagem, independentemente de quantos dispositivos eles estão usando no momento.

  • Grupo isolado

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

    Somente os clientes que pertencem a um determinado grupo recebem essa mensagem.

Clientes e a classe HubConnection

A classe HubConnection é um conceito de cliente SignalR, que representa a conexão do cliente com o servidor Hub. Ela é definida no namespace Microsoft.AspNetCore.SignalR.Client e faz parte do pacote de NuGet Microsoft.AspNetCore.SignalR.Client.

Você cria uma HubConnection usando o padrão do construtor e o tipo de HubConnectionBuilder correspondente. Considerando a rota do hub (ou System.Uri), você pode criar uma HubConnection. O construtor também pode especificar opções de configuração adicionais, incluindo o registro em log, o protocolo desejado, o encaminhamento de token de autenticação e a reconexão automática, entre outros.

A API HubConnection expõe as funções start e stop, que você usa para iniciar e parar a conexão com o servidor. Além disso, há recursos para streaming, chamar métodos de hub e assinar eventos.

Uma criação do HubConnection de exemplo

Para criar um objeto HubConnection do SDK do cliente do .NET SignalR, você poderá usar o tipo 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;
        }
    }
}

Chamar métodos de hub

Se for dada ao cliente uma instância de HubConnection do cliente que foi iniciada com sucesso, o cliente poderá chamar métodos em um hub usando as extensões InvokeAsync ou SendAsync. Se o método do hub retornar um Task<TResult>, o resultado de InvokeAsync<TResult> será do tipo TResult. Se o método do hub retornar Task, não haverá resultado. Tanto InvokeAsync quanto SendAsync requerem o nome do método de hub e de zero a 10 parâmetros.

  • InvokeAsync: invoca um método de hub no servidor usando o nome do método especificado e argumentos opcionais.
  • SendAsync: invoca um método de hub no servidor usando o nome do método especificado e argumentos opcionais. Esse método não aguarda uma resposta do receptor.

Um exemplo de invocação de método de hub

Quando SendNotificationAsync adiciona um método à classe de Consumer anterior, o SendNotificationAsync delega para a _hubConnection e chama o método NotifyAll no hub do servidor, dependendo da instância de Notification.

public Task SendNotificationAsync(string text) =>
    _hubConnection.InvokeAsync(
        "NotifyAll", new Notification(text, DateTime.UtcNow));

Tratar eventos

Para manipular eventos, registre um manipulador com a instância HubConnection. Chame uma das sobrecargas HubConnectionExtensions.On quando você conhece o nome do método de hub e tem de zero a oito parâmetros. O manipulador pode atender a qualquer uma das seguintes variações Action:

Como alternativa, você pode usar as APIs do manipulador assíncrono, que são Func<TResult> em que o TResult é uma variação de Task:

O resultado do registro de um manipulador de eventos é um IDisposable, que funciona como a assinatura. Para cancelar a assinatura do manipulador, chame Dispose.

Um exemplo de registro de evento

Atualizando a classe Consumer anterior, você se registra em um evento fornecendo um manipulador e chamando 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.
}

O método OnNotificationReceivedAsync é chamado quando a instância do hub do servidor aciona o evento "NotificationReceived".

Atualizações do pedido ao vivo de pizza da Contoso

O código do servidor para o aplicativo web precisa ter uma implementação do Hub e expor uma rota para os clientes. O Hub pode usar o identificador exclusivo do objeto de pedido para criar um grupo para acompanhamento. Todas as atualizações de alteração de status de pedidos poderiam ser comunicadas nesse grupo.

O código do cliente também precisaria ser atualizado para indicar que o aplicativo da Contoso Pizza é um aplicativo Blazor WebAssembly. Você pode usar o SDK do JavaScript ou o cliente .NET. Você substituiria a funcionalidade de sondagem do lado do cliente pelo código que cria um HubConnection e iniciaria a conexão com o servidor. À medida que navega até a página de acompanhamento de pedidos, o código teria que ingressar no grupo específico do pedido para onde as atualizações de alteração são enviadas. Você precisa fazer uma assinatura do evento para alterações de status do pedido e, em seguida, fazer o que for necessário.