Como funciona o SignalR do ASP.NET Core
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:
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 eT
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
ouTask<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
Todos os clientes conectados recebem essa mensagem, independentemente do grupo ao qual eles podem ou não pertencer.
Usuário isolado
Um único usuário recebe essa mensagem, independentemente de quantos dispositivos eles estão usando no momento.
Grupo isolado
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
:
- 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>
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
:
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>
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.