Usar hubs no ASP.NET Core SignalR

Por Rachel Appel e Kevin Griffin

A API SignalR Hubs permite que clientes conectados chamem métodos no servidor, facilitando a comunicação em tempo real. O servidor define métodos que são chamados pelo cliente e o cliente define métodos que são chamados pelo servidor. SignalR também permite a comunicação indireta entre clientes, em que o SignalR Hub faz a mediação. Essa abordagem permite enviar mensagens entre clientes individuais, grupos ou para todos os clientes conectados. O SignalR cuida de tudo o que é necessário para possibilitar a comunicação em tempo real entre cliente e servidor e entre servidor e cliente.

Este artigo descreve como configurar hubs, enviar mensagens aos clientes e permitir que os servidores lidem com os resultados dos clientes.

Configurar hubs do SignalR

Registre os serviços exigidos pelos SignalR hubs chamando o AddSignalR método no arquivo Program.cs :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

Configure os endpoints SignalR chamando o método MapHub no arquivo Program.cs:

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

Note

Os assemblies do lado do servidor do ASP.NET Core SignalR agora são instalados com o SDK do .NET Core. Para obter mais informações, consulte Assemblies SignalR na estrutura compartilhada.

Criar e usar hubs

Crie um hub declarando uma classe que herda de Hub. Adicione métodos public à classe para torná-los chamáveis pelos clientes:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

Note

Os hubs são transitórios:

  • Não armazene o estado em uma propriedade da classe de hub. Cada chamada de método de hub é executada em uma nova instância de hub.
  • Não instancie um hub diretamente por meio de injeção de dependência. Para enviar mensagens para um cliente de outro lugar em seu aplicativo, use um IHubContext.
  • Use await ao chamar métodos assíncronos que dependem de o hub permanecer ativo. Por exemplo, se você chamar um método como Clients.All.SendAsync(...) sem usar await, a chamada poderá falhar, e o método do hub poderá ser concluído antes que SendAsync seja concluído.

Note

Parâmetros do método hub, valores retornados e itens de fluxo podem ser tipos de união C# apenas com o padrão JsonHubProtocol. Os protocolos de hub MessagePack e Newtonsoft.Json não dão suporte a uniões.

Usar propriedades e métodos do objeto 'Context'

A classe Hub inclui uma propriedade Context que contém as seguintes propriedades com informações sobre a conexão:

Property Description
ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de conexão para cada conexão.
UserIdentifier Obtém o identificador de usuário. Por padrão, SignalR usa a ClaimTypes.NameIdentifier propriedade do ClaimsPrincipal associado à conexão como o identificador de usuário.
User Obtém o ClaimsPrincipal associado ao usuário atual.
Items Obtém uma coleção de chave/valor que pode ser usada para compartilhar dados dentro do escopo dessa conexão. Os dados podem ser armazenados nesta coleção e persistem ao longo da conexão entre diferentes invocações de métodos do hub.
Features Obtém a coleção de recursos disponíveis na conexão. No momento, essa coleção não é necessária na maioria dos cenários, portanto, a documentação detalhada ainda não está disponível.
ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é abortada.

A Hub.Context propriedade também contém os seguintes métodos:

Method Description
GetHttpContext Retorna o HttpContext para a conexão, ou null se a conexão não está associada a uma solicitação HTTP. Para conexões HTTP, use esse método para obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.
Abort Anula a conexão.

Use as propriedades e os métodos do objeto 'Clients'

A classe Hub inclui uma propriedade Clients que contém as seguintes propriedades para comunicação entre o servidor e o cliente:

Property Description
All Chama um método em todos os clientes conectados.
Caller Chama um método no cliente que invocou o método hub.
Others Chama um método em todos os clientes conectados, exceto no cliente que invocou o método.

A Hub.Clients propriedade também contém os seguintes métodos:

Method Description
AllExcept Chama um método em todos os clientes conectados, exceto nas conexões especificadas.
Client Chama um método em um cliente conectado específico.
Clients Chama um método em clientes conectados específicos.
Group Chama um método em todas as conexões no grupo especificado.
GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as conexões especificadas.
Groups Chama um método em vários grupos de conexões.
OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou o método hub.
User Chama um método em todas as conexões associadas a um usuário específico.
Users Chama um método em todas as conexões associadas aos usuários especificados.

Cada propriedade ou método retorna um objeto com um SendAsync método. O método SendAsync recebe o nome do método do cliente a ser chamado e quaisquer parâmetros.

O objeto retornado pelos métodos Client e Caller também contém um método InvokeAsync, que pode ser usado para aguardar um resultado do cliente.

Enviar mensagens para clientes

Para fazer chamadas para clientes específicos, use as propriedades do objeto Clients. No exemplo a seguir, existem três métodos de hub:

  • O SendMessage método envia uma mensagem para todos os clientes conectados usando a Clients.All propriedade.
  • O SendMessageToCaller método envia uma mensagem de volta ao chamador usando a Clients.Caller propriedade.
  • O SendMessageToGroup método envia uma mensagem a todos os clientes do SignalR Users grupo.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Usar hubs fortemente tipados

Uma desvantagem de usar o SendAsync método é que ele depende de uma cadeia de caracteres para especificar o método cliente a ser chamado. Esse design deixa o código aberto para erros de runtime se o nome do método estiver incorreto ou ausente do cliente.

Uma alternativa ao uso do SendAsync método é digitar fortemente a Hub classe com Hub<T>. No exemplo a seguir, o ChatHub método cliente é extraído em uma interface chamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

A interface pode ser usada para refatorar o exemplo anterior ChatHub para torná-lo fortemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do cliente. Essa abordagem impede problemas causados pelo uso de cadeias de caracteres porque Hub<T> só pode fornecer acesso aos métodos definidos na interface. O uso de um Hub<T> fortemente tipado impede o uso do método SendAsync.

Note

O sufixo Async não é removido dos nomes de método. A menos que um método de cliente seja definido com .on('MyMethodAsync'), não use MyMethodAsync como o nome.

Solicitar resultados do cliente

Além de fazer chamadas para clientes, o servidor pode solicitar um resultado de um cliente. Nesse cenário, o servidor usa o ISingleClientProxy.InvokeAsync método e o cliente retorna um resultado de seu .On manipulador.

Há duas maneiras de usar a API no servidor.

Você pode chamar Client(...) ou Caller na propriedade Clients em um método Hub:

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

Ou você pode chamar Client(...) em uma instância de IHubContext<T>:

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

Hubs fortemente tipados também podem retornar valores de métodos de interface:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

Os clientes retornam resultados nos seus manipuladores .On(...), conforme mostrado nas seções a seguir.

Cliente .NET

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Cliente TypeScript

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Cliente Java

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

Alterar o nome de um método de hub

Por padrão, um nome de método de hub do servidor é o nome do método .NET. Para alterar esse comportamento padrão para um método específico, use o atributo HubMethodName . O cliente deve usar esse nome em vez do nome do método .NET ao invocar o método:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Injetar serviços em um hub

Construtores de hub podem aceitar serviços da injeção de dependência como parâmetros, que podem ser armazenados em propriedades na classe para uso em um método de hub.

Ao injetar vários serviços em diferentes métodos de hub ou como uma forma alternativa de estruturar o código, os métodos de hub também podem aceitar serviços por meio da injeção de dependência. Por padrão, os parâmetros do método hub são inspecionados e resolvidos com base na injeção de dependência, se possível.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Se a resolução implícita de parâmetros a partir de serviços não seja desejada, você pode desativar esse comportamento com a opção de servidor DisableImplicitFromServicesParameters.

Para especificar de forma explícita quais parâmetros são resolvidos pela injeção de dependência nos métodos de hub, use a propriedade DisableImplicitFromServicesParameters. Especifique o atributo [FromServices] ou um atributo personalizado que implemente IFromServiceMetadata nos parâmetros do método de hub que devem ser resolvidos por injeção de dependência.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Note

Esse recurso usa IServiceProviderIsService, que é opcionalmente implementado em configurações de injeção de dependência. Se o contêiner de injeção de dependência do aplicativo não der suporte a esse recurso, não há suporte para a injeção de serviços em métodos de hub.

Suporte a serviços com chave na injeção de dependência

O mecanismo de serviços com chave permite que você registre e recupere serviços de injeção de dependência usando chaves. Um serviço é associado a uma chave chamando o AddKeyedSingleton método para registrá-lo. Como alternativa, você pode chamar o método AddKeyedScoped ou AddKeyedTransient.

Você acessa um serviço registrado especificando a chave com o atributo [FromKeyedServices]. O código a seguir mostra como usar serviços com chave:

using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

app.MapRazorPages();
app.MapHub<MyHub>("/myHub");

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

public class MyHub : Hub
{
    public void SmallCacheMethod([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }

    public void BigCacheMethod([FromKeyedServices("big")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Limitar invocações de streaming por conexão

MaximumParallelInvocationsPerClient controla o número de invocações de métodos de hub sem streaming que um cliente pode executar em paralelo antes de serem colocadas na fila. Ele não se aplica a invocações de hub de streaming. As invocações de streaming são excluídas intencionalmente porque espera-se que sejam de execução longa e simultâneas, de modo que um cliente possa iniciar qualquer número de fluxos simultâneos, independentemente dessa configuração.

Para impor um limite por conexão nas invocações de streaming, encapsule a transmissão dentro do próprio método do hub usando um auxiliar privado que incrementa um contador antes de produzir itens e decrementa o contador em um bloco finally:

using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

public class StreamingHub : Hub
{
    private static readonly ConcurrentDictionary<string, int> _activeStreams = new();
    private const int MaxConcurrentStreams = 2;

    public IAsyncEnumerable<int> Counter(
        int count,
        int delay,
        CancellationToken cancellationToken)
    {
        return WithLimit(Context.ConnectionId, GetCounter(count, delay, cancellationToken));
    }

    private async IAsyncEnumerable<int> GetCounter(
        int count,
        int delay,
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        for (var i = 0; i < count; i++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            yield return i;
            await Task.Delay(delay, cancellationToken);
        }
    }

    private async IAsyncEnumerable<T> WithLimit(
        string connectionId,
        IAsyncEnumerable<T> stream,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var current = _activeStreams.AddOrUpdate(
            connectionId,
            addValue: 1,
            updateValueFactory: (_, count) => count + 1);

        if (current > MaxConcurrentStreams)
        {
            Decrement(connectionId);
            throw new HubException(
                $"The connection is limited to {MaxConcurrentStreams} concurrent streaming invocations.");
        }

        try
        {
            await foreach (var item in stream.WithCancellation(cancellationToken))
            {
                yield return item;
            }
        }
        finally
        {
            Decrement(connectionId);
        }
    }

    private static void Decrement(string connectionId)
    {
        while (_activeStreams.TryGetValue(connectionId, out var current))
        {
            if (current <= 1)
            {
                if (_activeStreams.TryRemove(new KeyValuePair<string, int>(connectionId, current)))
                {
                    return;
                }
            }
            else if (_activeStreams.TryUpdate(connectionId, current - 1, current))
            {
                return;
            }
        }
    }
}

O ponto chave é que WithLimit encapsula o original IAsyncEnumerable<T> e mantém o contador elevado durante todo o tempo de vida do fluxo, não apenas até que o primeiro item seja produzido. O finally bloco é executado somente quando o cliente termina de consumir o fluxo, cancela-o ou a conexão é descartada.

Se os métodos do hub de streaming retornarem ChannelReader<T> em vez de IAsyncEnumerable<T>, um wrapper semelhante poderá ser aplicado. Ele deve usar o mesmo dicionário _activeStreams para que ambos os tipos de fluxo compartilhem um único limite de nível de conexão em vez de cada um manter sua própria contagem independente.

Note

O dicionário _activeStreams é static, de modo que seja compartilhado entre todas as instâncias do hub. Se preferir o estado gerenciado por DI, registre um serviço singleton que possua o dicionário e injete-o no construtor do hub.

Manipular eventos para uma conexão

A API Hubs do SignalR fornece os métodos virtuais OnConnectedAsync e OnDisconnectedAsync para gerenciar e acompanhar conexões. Substitua o método virtual OnConnectedAsync para executar ações quando um cliente se conectar ao hub, como adicioná-lo a um grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescreva o método virtual OnDisconnectedAsync para executar ações quando ocorrer a desconexão de um cliente. Se o cliente se desconectar intencionalmente, como chamando connection.stop(), o parâmetro exception será definido como null. No entanto, se o cliente se desconectar devido a um erro, como uma falha de rede, o parâmetro exception conterá uma exceção que descreve a falha:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

O RemoveFromGroupAsync método não precisa ser chamado dentro do OnDisconnectedAsync método porque ele é tratado automaticamente.

Gerenciar erros

Exceções geradas em métodos de hub são enviadas ao cliente que invocou o método . No cliente JavaScript, o invoke método retorna um objeto 'Promise' do JavaScript. Os clientes podem anexar um manipulador catch à promessa retornada ou usar try/catch com async/await para lidar com exceções:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

As conexões não são fechadas quando um hub lança uma exceção. Por padrão, SignalR retorna uma mensagem de erro genérica para o cliente, conforme mostrado no exemplo a seguir:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de dados em uma exceção disparada quando a conexão de banco de dados falha. Como medida de segurança, SignalR não expõe essas mensagens de erro detalhadas por padrão. Para obter mais informações sobre por que os detalhes da exceção são suprimidos, confira as Considerações de segurança no ASP.NET Core SignalR.

Se uma condição excepcional precisar ser propagada para o cliente, use a classe HubException. Se um HubException for lançado em um método de hub, SignalRenviará toda a mensagem de exceção para o cliente de forma não modificada:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Note

SignalR apenas envia a propriedade Message da exceção para o cliente. O rastreamento de pilha e outras propriedades na exceção não estão disponíveis para o cliente.

Por Rachel Appel e Kevin Griffin

A API SignalR Hubs permite que clientes conectados chamem métodos no servidor, facilitando a comunicação em tempo real. O servidor define métodos que são chamados pelo cliente e o cliente define métodos que são chamados pelo servidor. SignalR também possibilita a comunicação indireta cliente a cliente, sempre mediada pelo SignalR Hub, permitindo o envio de mensagens entre clientes individuais, grupos ou para todos os clientes conectados. O SignalR cuida de tudo o que é necessário para possibilitar a comunicação em tempo real entre cliente e servidor e entre servidor e cliente.

Configurar hubs do SignalR

Para registrar os serviços exigidos pelos hubs do SignalR, chame AddSignalR em Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

Para configurar pontos de extremidade do SignalR, chame MapHub, também em Program.cs:

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

Note

Os assemblies do lado do servidor do ASP.NET Core SignalR agora são instalados com o SDK do .NET Core. Para obter mais informações, consulte Assemblies SignalR na estrutura compartilhada.

Criar e usar hubs

Crie um hub declarando uma classe que herda de Hub. Adicione métodos public à classe para torná-los chamáveis pelos clientes:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

Note

Os hubs são transitórios:

  • Não armazene o estado em uma propriedade da classe de hub. Cada chamada de método de hub é executada em uma nova instância de hub.
  • Não instancie um hub diretamente por meio de injeção de dependência. Para enviar mensagens para um cliente de outro lugar em seu aplicativo, use um IHubContext.
  • Use await ao chamar métodos assíncronos que dependem de o hub permanecer ativo. Por exemplo, um método como Clients.All.SendAsync(...) pode falhar se for chamado sem await e o método de hub for concluído antes que SendAsync termine.

O objeto Context

A classe Hub inclui uma propriedade Context que contém as seguintes propriedades com informações sobre a conexão:

Property Description
ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de conexão para cada conexão.
UserIdentifier Obtém o identificador de usuário. Por padrão, SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado à conexão como o identificador de usuário.
User Obtém o ClaimsPrincipal associado ao usuário atual.
Items Obtém uma coleção de chave/valor que pode ser usada para compartilhar dados dentro do escopo dessa conexão. Os dados podem ser armazenados nesta coleção e persistirão para a conexão em diferentes invocações de método de hub.
Features Obtém a coleção de recursos disponíveis na conexão. Por enquanto, essa coleção não é necessária na maioria dos cenários, portanto, ainda não está documentada em detalhes.
ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é abortada.

Hub.Context também contém os seguintes métodos:

Method Description
GetHttpContext Retorna o HttpContext para a conexão, ou null se a conexão não está associada a uma solicitação HTTP. Para conexões HTTP, use esse método para obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.
Abort Anula a conexão.

O objeto Clients

A classe Hub inclui uma propriedade Clients que contém as seguintes propriedades para comunicação entre o servidor e o cliente:

Property Description
All Chama um método em todos os clientes conectados
Caller Chama um método no cliente que invocou o método de hub
Others Chama um método em todos os clientes conectados, exceto o cliente que invocou o método

Hub.Clients também contém os seguintes métodos:

Method Description
AllExcept Chama um método em todos os clientes conectados, exceto para as conexões especificadas
Client Chama um método em um cliente conectado específico
Clients Chama um método em clientes conectados específicos
Group Chama um método em todas as conexões no grupo especificado
GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as conexões especificadas
Groups Chama um método para múltiplos grupos de conexões
OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou o método de hub
User Chama um método em todas as conexões associadas a um usuário específico
Users Chama um método em todas as conexões associadas aos usuários especificados

Cada propriedade ou método nas tabelas anteriores retorna um objeto com um método SendAsync. O método SendAsync recebe o nome do método do cliente a ser chamado e quaisquer parâmetros.

O objeto retornado pelos métodos Client e Caller também contém um método InvokeAsync, que pode ser usado para aguardar um resultado do cliente.

Enviar mensagens para clientes

Para fazer chamadas para clientes específicos, use as propriedades do objeto Clients. No exemplo a seguir, existem três métodos de hub:

  • SendMessage envia uma mensagem para todos os clientes conectados, usando Clients.All.
  • SendMessageToCaller envia uma mensagem de volta para o chamador, usando Clients.Caller.
  • SendMessageToGroup envia uma mensagem para todos os clientes no grupo SignalR Users.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Hubs fortemente tipados

Uma desvantagem de usar SendAsync é que ele depende de uma cadeia de caracteres para especificar o método do cliente a ser chamado. Isso deixará o código aberto para erros de runtime se o nome do método estiver escrito incorretamente ou estiver ausente no cliente.

Uma alternativa ao uso do SendAsync é tipar fortemente a classe Hub com Hub<T>. No exemplo a seguir, o método do cliente ChatHub foi extraído para uma interface chamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Essa interface pode ser usada para refatorar o exemplo ChatHub anterior para ser fortemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do cliente. Isso impede problemas causados pelo uso de cadeias de caracteres, pois Hub<T> só pode fornecer acesso aos métodos definidos na interface. Usar um Hub<T> fortemente tipado desabilita a capacidade de usar SendAsync.

Note

O sufixo Async não é removido dos nomes de método. A menos que um método de cliente seja definido com .on('MyMethodAsync'), não use MyMethodAsync como o nome.

Resultados do cliente

Além de fazer chamadas para clientes, o servidor pode solicitar um resultado de um cliente. Isso requer que o servidor use ISingleClientProxy.InvokeAsync e o cliente retorne um resultado de seu manipulador .On.

Há duas maneiras de usar a API no servidor, a primeira é chamar Client(...) ou Caller na propriedade Clients em um método de hub:

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

A segunda maneira é chamar Client(...) em uma instância do IHubContext<T>:

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

Hubs fortemente tipados também podem retornar valores de métodos de interface:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

Os clientes retornam resultados em seus manipuladores .On(...), conforme mostrado abaixo:

Cliente .NET

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Cliente Typescript

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Cliente Java

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

Alterar o nome de um método de hub

Por padrão, um nome de método de hub do servidor é o nome do método .NET. Para alterar esse comportamento padrão para um método específico, use o atributo HubMethodName . O cliente deve usar esse nome em vez do nome do método .NET ao invocar o método:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Injetar serviços em um hub

Os construtores de hub podem aceitar serviços de DI como parâmetros, que podem ser armazenados em propriedades na classe para uso em um método de hub.

Ao injetar vários serviços em diferentes métodos de hub ou como uma maneira alternativa de programar, os métodos de hub também podem aceitar serviços da DI. Por padrão, os parâmetros do método de hub são inspecionados e resolvidos a partir da DI, se possível.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Se a resolução implícita de parâmetros de serviços não for desejada, desabilite-a com DisableImplicitFromServicesParameters. Para especificar explicitamente quais parâmetros são resolvidos a partir da DI em métodos de hub, use a opção DisableImplicitFromServicesParameters e use o atributo [FromServices] ou um atributo personalizado que implementa IFromServiceMetadata nos parâmetros de método de hub que devem ser resolvidos a partir da DI.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Note

Esse recurso usa IServiceProviderIsService, que é implementado opcionalmente pelas implementações de DI. Se o contêiner da DI do aplicativo não oferecer suporte a esse recurso, não há suporte para a injeção de serviços em métodos de hub.

Manipular eventos para uma conexão

A API Hubs do SignalR fornece os métodos virtuais OnConnectedAsync e OnDisconnectedAsync para gerenciar e acompanhar conexões. Substitua o método virtual OnConnectedAsync para executar ações quando um cliente se conectar ao hub, como adicioná-lo a um grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescreva o método virtual OnDisconnectedAsync para executar ações quando ocorrer a desconexão de um cliente. Se o cliente se desconectar intencionalmente, como chamando connection.stop(), o parâmetro exception será definido como null. No entanto, se o cliente se desconectar devido a um erro, como uma falha de rede, o parâmetro exception conterá uma exceção que descreve a falha:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync não precisa ser chamado em OnDisconnectedAsync, o processamento é feito automaticamente para você.

Gerenciar erros

Exceções geradas em métodos de hub são enviadas ao cliente que invocou o método . No cliente JavaScript, o método invoke retorna um JavaScript Promise. Os clientes podem anexar um manipulador catch à promessa retornada ou usar try/catch com async/await para lidar com exceções:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

As conexões não são fechadas quando um hub lança uma exceção. Por padrão, SignalR retorna uma mensagem de erro genérica para o cliente, conforme mostrado no exemplo a seguir:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de dados em uma exceção disparada quando a conexão de banco de dados falha. SignalR não expõe essas mensagens de erro detalhadas por padrão como uma medida de segurança. Para obter mais informações sobre por que os detalhes da exceção são suprimidos, confira as Considerações de segurança no ASP.NET Core SignalR.

Se uma condição excepcional precisar ser propagada para o cliente, use a classe HubException. Se um HubException for lançado em um método de hub, o SignalRenviará toda a mensagem de exceção para o cliente, sem modificação:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Note

SignalR apenas envia a propriedade Message da exceção para o cliente. O rastreamento de pilha e outras propriedades na exceção não estão disponíveis para o cliente.

Recursos adicionais

Por Rachel Appel e Kevin Griffin

A API SignalR Hubs permite que clientes conectados chamem métodos no servidor, facilitando a comunicação em tempo real. O servidor define métodos que são chamados pelo cliente e o cliente define métodos que são chamados pelo servidor. SignalR também possibilita a comunicação indireta cliente a cliente, sempre mediada pelo SignalR Hub, permitindo o envio de mensagens entre clientes individuais, grupos ou para todos os clientes conectados. O SignalR cuida de tudo o que é necessário para possibilitar a comunicação em tempo real entre cliente e servidor e entre servidor e cliente.

Configurar hubs do SignalR

Para registrar os serviços exigidos pelos hubs do SignalR, chame AddSignalR em Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

Para configurar pontos de extremidade do SignalR, chame MapHub, também em Program.cs:

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

Note

Os assemblies do lado do servidor do ASP.NET Core SignalR agora são instalados com o SDK do .NET Core. Para obter mais informações, consulte Assemblies SignalR na estrutura compartilhada.

Criar e usar hubs

Crie um hub declarando uma classe que herda de Hub. Adicione métodos public à classe para torná-los chamáveis pelos clientes:

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

Note

Os hubs são transitórios:

  • Não armazene o estado em uma propriedade da classe de hub. Cada chamada de método de hub é executada em uma nova instância de hub.
  • Não instancie um hub diretamente por meio de injeção de dependência. Para enviar mensagens para um cliente de outro lugar em seu aplicativo, use um IHubContext.
  • Use await ao chamar métodos assíncronos que dependem de o hub permanecer ativo. Por exemplo, um método como Clients.All.SendAsync(...) pode falhar se for chamado sem await e o método de hub for concluído antes que SendAsync termine.

O objeto Context

A classe Hub inclui uma propriedade Context que contém as seguintes propriedades com informações sobre a conexão:

Property Description
ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de conexão para cada conexão.
UserIdentifier Obtém o identificador de usuário. Por padrão, SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado à conexão como o identificador de usuário.
User Obtém o ClaimsPrincipal associado ao usuário atual.
Items Obtém uma coleção de chave/valor que pode ser usada para compartilhar dados dentro do escopo dessa conexão. Os dados podem ser armazenados nesta coleção e persistirão para a conexão em diferentes invocações de método de hub.
Features Obtém a coleção de recursos disponíveis na conexão. Por enquanto, essa coleção não é necessária na maioria dos cenários, portanto, ainda não está documentada em detalhes.
ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é abortada.

Hub.Context também contém os seguintes métodos:

Method Description
GetHttpContext Retorna o HttpContext para a conexão, ou null se a conexão não está associada a uma solicitação HTTP. Para conexões HTTP, use esse método para obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.
Abort Anula a conexão.

O objeto Clients

A classe Hub inclui uma propriedade Clients que contém as seguintes propriedades para comunicação entre o servidor e o cliente:

Property Description
All Chama um método em todos os clientes conectados
Caller Chama um método no cliente que invocou o método de hub
Others Chama um método em todos os clientes conectados, exceto o cliente que invocou o método

Hub.Clients também contém os seguintes métodos:

Method Description
AllExcept Chama um método em todos os clientes conectados, exceto para as conexões especificadas
Client Chama um método em um cliente conectado específico
Clients Chama um método em clientes conectados específicos
Group Chama um método em todas as conexões no grupo especificado
GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as conexões especificadas
Groups Chama um método para múltiplos grupos de conexões
OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou o método de hub
User Chama um método em todas as conexões associadas a um usuário específico
Users Chama um método em todas as conexões associadas aos usuários especificados

Cada propriedade ou método nas tabelas anteriores retorna um objeto com um método SendAsync. O método SendAsync recebe o nome do método do cliente a ser chamado e quaisquer parâmetros.

Enviar mensagens para clientes

Para fazer chamadas para clientes específicos, use as propriedades do objeto Clients. No exemplo a seguir, existem três métodos de hub:

  • SendMessage envia uma mensagem para todos os clientes conectados, usando Clients.All.
  • SendMessageToCaller envia uma mensagem de volta para o chamador, usando Clients.Caller.
  • SendMessageToGroup envia uma mensagem para todos os clientes no grupo SignalR Users.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Hubs fortemente tipados

Uma desvantagem de usar SendAsync é que ele depende de uma cadeia de caracteres para especificar o método do cliente a ser chamado. Isso deixará o código aberto para erros de runtime se o nome do método estiver escrito incorretamente ou estiver ausente no cliente.

Uma alternativa ao uso do SendAsync é tipar fortemente a classe Hub com Hub<T>. No exemplo a seguir, o método do cliente ChatHub foi extraído para uma interface chamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Essa interface pode ser usada para refatorar o exemplo ChatHub anterior para ser fortemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do cliente. Isso impede problemas causados pelo uso de cadeias de caracteres, pois Hub<T> só pode fornecer acesso aos métodos definidos na interface. Usar um Hub<T> fortemente tipado desabilita a capacidade de usar SendAsync.

Note

O sufixo Async não é removido dos nomes de método. A menos que um método de cliente seja definido com .on('MyMethodAsync'), não use MyMethodAsync como o nome.

Alterar o nome de um método de hub

Por padrão, um nome de método de hub do servidor é o nome do método .NET. Para alterar esse comportamento padrão para um método específico, use o atributo HubMethodName . O cliente deve usar esse nome em vez do nome do método .NET ao invocar o método:

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Manipular eventos para uma conexão

A API Hubs do SignalR fornece os métodos virtuais OnConnectedAsync e OnDisconnectedAsync para gerenciar e acompanhar conexões. Substitua o método virtual OnConnectedAsync para executar ações quando um cliente se conectar ao hub, como adicioná-lo a um grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescreva o método virtual OnDisconnectedAsync para executar ações quando ocorrer a desconexão de um cliente. Se o cliente se desconectar intencionalmente, como chamando connection.stop(), o parâmetro exception será definido como null. No entanto, se o cliente se desconectar devido a um erro, como uma falha de rede, o parâmetro exception conterá uma exceção que descreve a falha:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync não precisa ser chamado em OnDisconnectedAsync, o processamento é feito automaticamente para você.

Gerenciar erros

Exceções geradas em métodos de hub são enviadas ao cliente que invocou o método . No cliente JavaScript, o método invoke retorna um JavaScript Promise. Os clientes podem anexar um manipulador catch à promessa retornada ou usar try/catch com async/await para lidar com exceções:

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

As conexões não são fechadas quando um hub lança uma exceção. Por padrão, SignalR retorna uma mensagem de erro genérica para o cliente, conforme mostrado no exemplo a seguir:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de dados em uma exceção disparada quando a conexão de banco de dados falha. SignalR não expõe essas mensagens de erro detalhadas por padrão como uma medida de segurança. Para obter mais informações sobre por que os detalhes da exceção são suprimidos, confira as Considerações de segurança no ASP.NET Core SignalR.

Se uma condição excepcional precisar ser propagada para o cliente, use a classe HubException. Se um HubException for lançado em um método de hub, o SignalRenviará toda a mensagem de exceção para o cliente, sem modificação:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Note

SignalR apenas envia a propriedade Message da exceção para o cliente. O rastreamento de pilha e outras propriedades na exceção não estão disponíveis para o cliente.

Recursos adicionais

Por Rachel Appel e Kevin Griffin

Exibir ou baixar código de exemplo (como baixar)

O que é um hub do SignalR

A API SignalR Hubs permite que clientes conectados chamem métodos no servidor, facilitando a comunicação em tempo real. O servidor define métodos que são chamados pelo cliente e o cliente define métodos que são chamados pelo servidor. SignalR também possibilita a comunicação indireta cliente a cliente, sempre mediada pelo SignalR Hub, permitindo o envio de mensagens entre clientes individuais, grupos ou para todos os clientes conectados. O SignalR cuida de tudo o que é necessário para possibilitar a comunicação em tempo real entre cliente e servidor e entre servidor e cliente.

Configurar hubs do SignalR

O middleware SignalR requer alguns serviços, que são configurados chamando AddSignalR:

services.AddSignalR();

Ao adicionar a funcionalidade SignalR a um aplicativo ASP.NET Core, configure rotas SignalR chamando MapHub na chamada de retorno Startup.Configure do método UseEndpoints:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/chathub");
});

Note

Os assemblies do lado do servidor do ASP.NET Core SignalR agora são instalados com o SDK do .NET Core. Para obter mais informações, consulte Assemblies SignalR na estrutura compartilhada.

Criar e usar hubs

Crie um hub declarando uma classe que herda de Hub e adicione métodos públicos a ele. Os clientes podem chamar métodos definidos como public:

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Você pode especificar um tipo de retorno e parâmetros, incluindo tipos complexos e matrizes, como faria em qualquer método C#. SignalR manipula a serialização e desserialização de objetos complexos e matrizes em seus parâmetros e valores retornados.

Note

Os hubs são transitórios:

  • Não armazene o estado em uma propriedade na classe de hub. Toda chamada de método de hub é executada em uma nova instância de hub.
  • Não instancie um hub diretamente por meio de injeção de dependência. Para enviar mensagens para um cliente de outro lugar em seu aplicativo, use um IHubContext.
  • Use await ao chamar métodos assíncronos que dependem de o hub permanecer ativo. Por exemplo, um método como Clients.All.SendAsync(...) pode falhar se for chamado sem await e o método de hub for concluído antes que SendAsync termine.

O objeto Context

A classe Hub tem uma propriedade Context que contém as seguintes propriedades com informações sobre a conexão:

Property Description
ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de conexão para cada conexão.
UserIdentifier Obtém o identificador de usuário. Por padrão, SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado à conexão como o identificador de usuário.
User Obtém o ClaimsPrincipal associado ao usuário atual.
Items Obtém uma coleção de chave/valor que pode ser usada para compartilhar dados dentro do escopo dessa conexão. Os dados podem ser armazenados nesta coleção e persistirão para a conexão em diferentes invocações de método de hub.
Features Obtém a coleção de recursos disponíveis na conexão. Por enquanto, essa coleção não é necessária na maioria dos cenários, portanto, ainda não está documentada em detalhes.
ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é abortada.

Hub.Context também contém os seguintes métodos:

Method Description
GetHttpContext Retorna o HttpContext para a conexão, ou null se a conexão não está associada a uma solicitação HTTP. Para conexões HTTP, você pode usar esse método para obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.
Abort Anula a conexão.

O objeto Clients

A classe Hub tem uma propriedade Clients que contém as seguintes propriedades para comunicação entre o servidor e o cliente:

Property Description
All Chama um método em todos os clientes conectados
Caller Chama um método no cliente que invocou o método de hub
Others Chama um método em todos os clientes conectados, exceto o cliente que invocou o método

Hub.Clients também contém os seguintes métodos:

Method Description
AllExcept Chama um método em todos os clientes conectados, exceto para as conexões especificadas
Client Chama um método em um cliente conectado específico
Clients Chama um método em clientes conectados específicos
Group Chama um método em todas as conexões no grupo especificado
GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as conexões especificadas
Groups Chama um método para múltiplos grupos de conexões
OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou o método de hub
User Chama um método em todas as conexões associadas a um usuário específico
Users Chama um método em todas as conexões associadas aos usuários especificados

Cada propriedade ou método nas tabelas anteriores retorna um objeto com um método SendAsync. O método SendAsync permite que você forneça o nome e os parâmetros do método do cliente a ser chamado.

Enviar mensagens para clientes

Para fazer chamadas para clientes específicos, use as propriedades do objeto Clients. No exemplo a seguir, existem três Métodos de hub:

  • SendMessage envia uma mensagem para todos os clientes conectados, usando Clients.All.
  • SendMessageToCaller envia uma mensagem de volta para o chamador, usando Clients.Caller.
  • SendMessageToGroup envia uma mensagem para todos os clientes no grupo SignalR Users.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

Hubs fortemente tipados

Uma desvantagem de usar SendAsync é que ele depende de uma cadeia de caracteres mágica para especificar o método do cliente a ser chamado. Isso deixará o código aberto para erros de runtime se o nome do método estiver escrito incorretamente ou estiver ausente no cliente.

Uma alternativa ao uso do SendAsync é tipar fortemente a Hub com Hub<T>. No exemplo a seguir, os métodos do cliente ChatHub foram extraídos para uma interface chamada IChatClient.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Essa interface pode ser usada para refatorar o exemplo ChatHub anterior:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do cliente. Isso impede problemas causados pelo uso de cadeias de caracteres mágicas, pois Hub<T> só pode fornecer acesso aos métodos definidos na interface.

Usar um Hub<T> fortemente tipado desabilita a capacidade de usar SendAsync. Todos os métodos definidos na interface ainda podem ser definidos como assíncronos. Na verdade, cada um desses métodos deve retornar um Task. Como é uma interface, não use a palavra-chave async. Por exemplo:

public interface IClient
{
    Task ClientMethod();
}

Note

O sufixo Async não é removido do nome do método. A menos que o método do cliente seja definido com .on('MyMethodAsync'), você não deve usar MyMethodAsync como um nome.

Alterar o nome de um método de hub

Por padrão, um nome de método de hub do servidor é o nome do método .NET. No entanto, você pode usar o atributo HubMethodName para alterar esse padrão e especificar manualmente um nome para o método. O cliente deve usar esse nome, em vez do nome do método .NET, ao invocar o método :

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

Manipular eventos para uma conexão

A API Hubs do SignalR fornece os métodos virtuais OnConnectedAsync e OnDisconnectedAsync para gerenciar e acompanhar conexões. Substitua o método virtual OnConnectedAsync para executar ações quando um cliente se conectar ao Hub, como adicioná-lo a um grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescreva o método virtual OnDisconnectedAsync para executar ações quando ocorrer a desconexão de um cliente. Se o cliente se desconectar intencionalmente (chamando connection.stop(), por exemplo), o parâmetro exception será null. No entanto, se o cliente estiver desconectado devido a um erro (como uma falha de rede), o parâmetro exception conterá uma exceção que descreve a falha:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync não precisa ser chamado em OnDisconnectedAsync, o processamento é feito automaticamente para você.

Warning

Aviso de segurança: expor ConnectionId pode levar a usurpação de identidade mal-intencionada se a versão do servidor ou do cliente do SignalR for ASP.NET Core 2.2 ou anterior.

Gerenciar erros

Exceções geradas em seus métodos de hub são enviadas ao cliente que invocou o método . No cliente JavaScript, o método invoke retorna um JavaScript Promise. Quando o cliente recebe um erro com um manipulador anexado à promessa usando catch, ele é invocado e passado como um objeto JavaScript Error:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

Se o Hub gerar uma exceção, as conexões não serão fechadas. Por padrão, SignalR retorna uma mensagem de erro genérica para o cliente. Por exemplo:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de dados em uma exceção disparada quando a conexão de banco de dados falha. SignalR não expõe essas mensagens de erro detalhadas por padrão como uma medida de segurança. Para obter mais informações sobre por que os detalhes da exceção são suprimidos, confira as Considerações de segurança no ASP.NET Core SignalR.

Se você tiver uma condição excepcional que deseja propagar para o cliente, poderá usar a classe HubException. Se você lançar um HubException a partir do seu método de hub, SignalRenviará toda a mensagem para o cliente, sem modificações:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

Note

SignalR apenas envia a propriedade Message da exceção para o cliente. O rastreamento de pilha e outras propriedades na exceção não estão disponíveis para o cliente.

Recursos adicionais

Por Rachel Appel e Kevin Griffin

Exibir ou baixar código de exemplo (como baixar)

O que é um hub do SignalR

A API SignalR Hubs permite que clientes conectados chamem métodos no servidor, facilitando a comunicação em tempo real. O servidor define métodos que são chamados pelo cliente e o cliente define métodos que são chamados pelo servidor. SignalR também possibilita a comunicação indireta cliente a cliente, sempre mediada pelo SignalR Hub, permitindo o envio de mensagens entre clientes individuais, grupos ou para todos os clientes conectados. O SignalR cuida de tudo o que é necessário para possibilitar a comunicação em tempo real entre cliente e servidor e entre servidor e cliente.

Configurar hubs do SignalR

O middleware SignalR requer alguns serviços, que são configurados chamando AddSignalR:

services.AddSignalR();

Ao adicionar a funcionalidade SignalR a um aplicativo ASP.NET Core, configure rotas SignalR chamando UseSignalR no método Startup.Configure:

app.UseSignalR(route =>
{
    route.MapHub<ChatHub>("/chathub");
});

Criar e usar hubs

Crie um hub declarando uma classe que herda de Hub e adicione métodos públicos a ele. Os clientes podem chamar métodos definidos como public:

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Você pode especificar um tipo de retorno e parâmetros, incluindo tipos complexos e matrizes, como faria em qualquer método C#. SignalR manipula a serialização e desserialização de objetos complexos e matrizes em seus parâmetros e valores retornados.

Note

Os hubs são transitórios:

  • Não armazene o estado em uma propriedade na classe de hub. Toda chamada de método de hub é executada em uma nova instância de hub.
  • Não instancie um hub diretamente por meio de injeção de dependência. Para enviar mensagens para um cliente de outro lugar em seu aplicativo, use um IHubContext.
  • Use await ao chamar métodos assíncronos que dependem de o hub permanecer ativo. Por exemplo, um método como Clients.All.SendAsync(...) pode falhar se for chamado sem await e o método de hub for concluído antes que SendAsync termine.

O objeto Context

A classe Hub tem uma propriedade Context que contém as seguintes propriedades com informações sobre a conexão:

Property Description
ConnectionId Obtém a ID exclusiva para a conexão, atribuída por SignalR. Há uma ID de conexão para cada conexão.
UserIdentifier Obtém o identificador de usuário. Por padrão, SignalR usa o ClaimTypes.NameIdentifier do ClaimsPrincipal associado à conexão como o identificador de usuário.
User Obtém o ClaimsPrincipal associado ao usuário atual.
Items Obtém uma coleção de chave/valor que pode ser usada para compartilhar dados dentro do escopo dessa conexão. Os dados podem ser armazenados nesta coleção e persistirão para a conexão em diferentes invocações de método de hub.
Features Obtém a coleção de recursos disponíveis na conexão. Por enquanto, essa coleção não é necessária na maioria dos cenários, portanto, ainda não está documentada em detalhes.
ConnectionAborted Obtém um CancellationToken que notifica quando a conexão é abortada.

Hub.Context também contém os seguintes métodos:

Method Description
GetHttpContext Retorna o HttpContext para a conexão, ou null se a conexão não está associada a uma solicitação HTTP. Para conexões HTTP, você pode usar esse método para obter informações como cabeçalhos HTTP e cadeias de caracteres de consulta.
Abort Anula a conexão.

O objeto Clients

A classe Hub tem uma propriedade Clients que contém as seguintes propriedades para comunicação entre o servidor e o cliente:

Property Description
All Chama um método em todos os clientes conectados
Caller Chama um método no cliente que invocou o método de hub
Others Chama um método em todos os clientes conectados, exceto o cliente que invocou o método

Hub.Clients também contém os seguintes métodos:

Method Description
AllExcept Chama um método em todos os clientes conectados, exceto para as conexões especificadas
Client Chama um método em um cliente conectado específico
Clients Chama um método em clientes conectados específicos
Group Chama um método em todas as conexões no grupo especificado
GroupExcept Chama um método em todas as conexões no grupo especificado, exceto as conexões especificadas
Groups Chama um método para múltiplos grupos de conexões
OthersInGroup Chama um método em um grupo de conexões, excluindo o cliente que invocou o método de hub
User Chama um método em todas as conexões associadas a um usuário específico
Users Chama um método em todas as conexões associadas aos usuários especificados

Cada propriedade ou método nas tabelas anteriores retorna um objeto com um método SendAsync. O método SendAsync permite que você forneça o nome e os parâmetros do método do cliente a ser chamado.

Enviar mensagens para clientes

Para fazer chamadas para clientes específicos, use as propriedades do objeto Clients. No exemplo a seguir, existem três Métodos de hub:

  • SendMessage envia uma mensagem para todos os clientes conectados, usando Clients.All.
  • SendMessageToCaller envia uma mensagem de volta para o chamador, usando Clients.Caller.
  • SendMessageToGroup envia uma mensagem para todos os clientes no grupo SignalR Users.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

Hubs fortemente tipados

Uma desvantagem de usar SendAsync é que ele depende de uma cadeia de caracteres mágica para especificar o método do cliente a ser chamado. Isso deixará o código aberto para erros de runtime se o nome do método estiver escrito incorretamente ou estiver ausente no cliente.

Uma alternativa ao uso do SendAsync é tipar fortemente a Hub com Hub<T>. No exemplo a seguir, os métodos do cliente ChatHub foram extraídos para uma interface chamada IChatClient.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Essa interface pode ser usada para refatorar o exemplo ChatHub anterior:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

Usar Hub<IChatClient> habilita a verificação em tempo de compilação dos métodos do cliente. Isso impede problemas causados pelo uso de cadeias de caracteres mágicas, pois Hub<T> só pode fornecer acesso aos métodos definidos na interface.

Usar um Hub<T> fortemente tipado desabilita a capacidade de usar SendAsync. Todos os métodos definidos na interface ainda podem ser definidos como assíncronos. Na verdade, cada um desses métodos deve retornar um Task. Como é uma interface, não use a palavra-chave async. Por exemplo:

public interface IClient
{
    Task ClientMethod();
}

Note

O sufixo Async não é removido do nome do método. A menos que o método do cliente seja definido com .on('MyMethodAsync'), você não deve usar MyMethodAsync como um nome.

Alterar o nome de um método de hub

Por padrão, um nome de método de hub do servidor é o nome do método .NET. No entanto, você pode usar o atributo HubMethodName para alterar esse padrão e especificar manualmente um nome para o método. O cliente deve usar esse nome, em vez do nome do método .NET, ao invocar o método :

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

Manipular eventos para uma conexão

A API Hubs do SignalR fornece os métodos virtuais OnConnectedAsync e OnDisconnectedAsync para gerenciar e acompanhar conexões. Substitua o método virtual OnConnectedAsync para executar ações quando um cliente se conectar ao Hub, como adicioná-lo a um grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescreva o método virtual OnDisconnectedAsync para executar ações quando ocorrer a desconexão de um cliente. Se o cliente se desconectar intencionalmente (chamando connection.stop(), por exemplo), o parâmetro exception será null. No entanto, se o cliente estiver desconectado devido a um erro (como uma falha de rede), o parâmetro exception conterá uma exceção que descreve a falha:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync não precisa ser chamado em OnDisconnectedAsync, o processamento é feito automaticamente para você.

Warning

Aviso de segurança: expor ConnectionId pode levar a usurpação de identidade mal-intencionada se a versão do servidor ou do cliente do SignalR for ASP.NET Core 2.2 ou anterior.

Gerenciar erros

Exceções geradas em seus métodos de hub são enviadas ao cliente que invocou o método . No cliente JavaScript, o método invoke retorna um JavaScript Promise. Quando o cliente recebe um erro com um manipulador anexado à promessa usando catch, ele é invocado e passado como um objeto JavaScript Error:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

Se o Hub gerar uma exceção, as conexões não serão fechadas. Por padrão, SignalR retorna uma mensagem de erro genérica para o cliente. Por exemplo:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

Exceções inesperadas geralmente contêm informações confidenciais, como o nome de um servidor de banco de dados em uma exceção disparada quando a conexão de banco de dados falha. SignalR não expõe essas mensagens de erro detalhadas por padrão como uma medida de segurança. Para obter mais informações sobre por que os detalhes da exceção são suprimidos, confira as Considerações de segurança no ASP.NET Core SignalR.

Se você tiver uma condição excepcional que deseja propagar para o cliente, poderá usar a classe HubException. Se você lançar um HubException a partir do seu método de hub, SignalRenviará toda a mensagem para o cliente, sem modificações:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

Note

SignalR apenas envia a propriedade Message da exceção para o cliente. O rastreamento de pilha e outras propriedades na exceção não estão disponíveis para o cliente.

Recursos adicionais