Cliente .NET SignalR do ASP.NET Core

A biblioteca de clientes .NET SignalR do ASP.NET Core permite que você se comunique com hubs SignalR de aplicativos .NET.

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

O exemplo de código neste artigo é um aplicativo WPF que usa o cliente .NET SignalR do ASP.NET Core.

Instalar o pacote SignalR do cliente .NET

O pacote Microsoft.AspNetCore.SignalR.Client é necessário para que os clientes .NET se conectem aos hubs SignalR.

Para instalar a biblioteca de clientes, execute o seguinte comando na janela Console do gerenciador de pacotes:

Install-Package Microsoft.AspNetCore.SignalR.Client

Conectar-se a um hub

Para estabelecer uma conexão, crie um HubConnectionBuilder e chame Build. A URL do hub, o protocolo, o tipo de transporte, o nível de log, os cabeçalhos e outras opções podem ser configurados durante a criação de uma conexão. Configure as opções necessárias inserindo qualquer um dos métodos HubConnectionBuilder em Build. Inicie a conexão com StartAsync.

using System;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.AspNetCore.SignalR.Client;

namespace SignalRChatClient
{
    public partial class MainWindow : Window
    {
        HubConnection connection;
        public MainWindow()
        {
            InitializeComponent();

            connection = new HubConnectionBuilder()
                .WithUrl("http://localhost:53353/ChatHub")
                .Build();

            connection.Closed += async (error) =>
            {
                await Task.Delay(new Random().Next(0,5) * 1000);
                await connection.StartAsync();
            };
        }

        private async void connectButton_Click(object sender, RoutedEventArgs e)
        {
            connection.On<string, string>("ReceiveMessage", (user, message) =>
            {
                this.Dispatcher.Invoke(() =>
                {
                   var newMessage = $"{user}: {message}";
                   messagesList.Items.Add(newMessage);
                });
            });

            try
            {
                await connection.StartAsync();
                messagesList.Items.Add("Connection started");
                connectButton.IsEnabled = false;
                sendButton.IsEnabled = true;
            }
            catch (Exception ex)
            {
                messagesList.Items.Add(ex.Message);
            }
        }

        private async void sendButton_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                await connection.InvokeAsync("SendMessage", 
                    userTextBox.Text, messageTextBox.Text);
            }
            catch (Exception ex)
            {                
                messagesList.Items.Add(ex.Message);                
            }
        }
    }
}

Manipular conexão perdida

Reconectar automaticamente

O HubConnection pode ser configurado para reconectar automaticamente usando o método WithAutomaticReconnect no HubConnectionBuilder. Ele não se reconectará automaticamente por padrão.

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect()
    .Build();

Sem parâmetros, WithAutomaticReconnect() configura o cliente para aguardar 0, 2, 10 e 30 segundos, respectivamente, antes de tentar cada tentativa de reconexão, parando após quatro tentativas com falha.

Antes de iniciar qualquer tentativa de reconexão, o HubConnection fará a transição para o estado HubConnectionState.Reconnecting e disparará o evento Reconnecting. Isso oferece uma oportunidade de avisar os usuários de que a conexão foi perdida e desabilitar elementos da interface do usuário. Aplicativos não interativos podem começar a enfileirar ou soltar mensagens.

connection.Reconnecting += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Reconnecting);

    // Notify users the connection was lost and the client is reconnecting.
    // Start queuing or dropping messages.

    return Task.CompletedTask;
};

Se o cliente se reconectar com êxito em suas quatro primeiras tentativas, o HubConnection fará a transição de volta para o estado Connected e disparará o evento Reconnected. Isso oferece uma oportunidade de informar aos usuários que a conexão foi restabelecida e desativar qualquer mensagem na fila.

Como a conexão parece totalmente nova para o servidor, um novo ConnectionId será fornecido aos manipuladores de eventos Reconnected.

Aviso

O parâmetro connectionId do manipulador de eventos Reconnected será nulo se o HubConnection tiver sido configurado para ignorar a negociação.

connection.Reconnected += connectionId =>
{
    Debug.Assert(connection.State == HubConnectionState.Connected);

    // Notify users the connection was reestablished.
    // Start dequeuing messages queued while reconnecting if any.

    return Task.CompletedTask;
};

WithAutomaticReconnect() não configurará o HubConnection para repetir falhas iniciais de início, portanto, as falhas de inicialização precisam ser tratadas manualmente:

public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token)
{
    // Keep trying to until we can start or the token is canceled.
    while (true)
    {
        try
        {
            await connection.StartAsync(token);
            Debug.Assert(connection.State == HubConnectionState.Connected);
            return true;
        }
        catch when (token.IsCancellationRequested)
        {
            return false;
        }
        catch
        {
            // Failed to connect, trying again in 5000 ms.
            Debug.Assert(connection.State == HubConnectionState.Disconnected);
            await Task.Delay(5000);
        }
    }
}

Se o cliente não se reconectar com êxito em suas quatro primeiras tentativas, o HubConnection fará a transição para o estado Disconnected e disparará o evento Closed. Isso oferece uma oportunidade de tentar reiniciar a conexão manualmente ou informar aos usuários que a conexão foi perdida permanentemente.

connection.Closed += error =>
{
    Debug.Assert(connection.State == HubConnectionState.Disconnected);

    // Notify users the connection has been closed or manually try to restart the connection.

    return Task.CompletedTask;
};

Para configurar um número personalizado de tentativas de reconexão antes de desconectar ou alterar o tempo de reconexão, WithAutomaticReconnect aceita uma matriz de números que representam o atraso em milissegundos para aguardar antes de iniciar cada tentativa de reconexão.

HubConnection connection= new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
    .Build();

    // .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }) yields the default behavior.

O exemplo anterior configura o HubConnection para começar a tentar se conectar novamente imediatamente após a conexão ser perdida. Isso também é verdadeiro para a configuração padrão.

Se a primeira tentativa de reconexão falhar, a segunda tentativa de reconexão também será iniciada imediatamente em vez de aguardar 2 segundos como ocorreria na configuração padrão.

Se a segunda tentativa de reconexão falhar, a terceira tentativa de reconexão será iniciada em 10 segundos, o que é, novamente, como a configuração padrão.

Em seguida, o comportamento personalizado diverge novamente do comportamento padrão, parando após a terceira falha de tentativa de reconexão. Na configuração padrão, haveria mais uma tentativa de reconexão em mais 30 segundos.

Se você quiser ter ainda mais controle sobre o tempo e o número de tentativas de reconexão automática, WithAutomaticReconnect aceitará um objeto que implementa a interface IRetryPolicy, que tem um único método chamado NextRetryDelay.

NextRetryDelay usa um único argumento com o tipo RetryContext. O RetryContext tem três propriedades: PreviousRetryCount, ElapsedTime e RetryReason, que são um long, um TimeSpan e um Exception, respectivamente. Antes da primeira tentativa de reconexão, PreviousRetryCount e ElapsedTime serão zero, e RetryReason será a Exceção que causou a perda da conexão. Após cada tentativa de repetição com falha, PreviousRetryCount será incrementado em um, ElapsedTime será atualizado para refletir o tempo gasto na reconexão até agora e RetryReason será a Exceção que causou a falha da última tentativa de reconexão.

NextRetryDelay deve retornar um TimeSpan que representa o tempo de espera antes da próxima tentativa de reconexão ou null se o HubConnection deve parar de se reconectar.

public class RandomRetryPolicy : IRetryPolicy
{
    private readonly Random _random = new Random();

    public TimeSpan? NextRetryDelay(RetryContext retryContext)
    {
        // If we've been reconnecting for less than 60 seconds so far,
        // wait between 0 and 10 seconds before the next reconnect attempt.
        if (retryContext.ElapsedTime < TimeSpan.FromSeconds(60))
        {
            return TimeSpan.FromSeconds(_random.NextDouble() * 10);
        }
        else
        {
            // If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
            return null;
        }
    }
}
HubConnection connection = new HubConnectionBuilder()
    .WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
    .WithAutomaticReconnect(new RandomRetryPolicy())
    .Build();

Como alternativa, você pode escrever código que reconectará o cliente manualmente, conforme demonstrado em Reconectar manualmente.

Reconectar manualmente

Aviso

Antes da versão 3.0, o cliente .NET para SignalR não se reconecta automaticamente. Você deve escrever um código que reconecte o cliente manualmente.

Use o evento Closed para responder a uma conexão perdida. Por exemplo, talvez você queira automatizar a reconexão.

O evento Closed requer um delegado que retorna um Task, que permite que o código assíncrono seja executado sem usar async void. Para satisfazer a assinatura de delegado em um manipulador de eventos Closed que é executado de forma síncrona, retorne Task.CompletedTask:

connection.Closed += (error) => {
    // Do your close logic.
    return Task.CompletedTask;
};

O principal motivo para o suporte assíncrono é para que você possa reiniciar a conexão. Iniciar uma conexão é uma ação assíncrona.

Em um manipulador Closed que reinicia a conexão, considere aguardar algum atraso aleatório para evitar sobrecarregar o servidor, conforme mostrado no exemplo a seguir:

connection.Closed += async (error) =>
{
    await Task.Delay(new Random().Next(0,5) * 1000);
    await connection.StartAsync();
};

Chamar métodos de hub do cliente

InvokeAsync chama métodos no hub. Passe o nome do método do hub e todos os argumentos definidos no método de hub para InvokeAsync. SignalR é assíncrono, portanto, use async e await ao fazer as chamadas.

await connection.InvokeAsync("SendMessage", 
    userTextBox.Text, messageTextBox.Text);

O método InvokeAsync retorna um Task que é concluído quando o método de servidor retorna. O valor retornado, se houver, é fornecido como o resultado do Task. Todas as exceções geradas pelo método no servidor produzem um Task com falha. Use a sintaxe await para aguardar a conclusão do método de servidor e a sintaxe try...catch para tratar erros.

O método SendAsync retorna um Task que é concluído quando a mensagem é enviada ao servidor. Nenhum valor retornado é fornecido, pois este Task não aguarda até que o método de servidor seja concluído. Todas as exceções geradas no cliente ao enviar a mensagem produzem um Task com falha. Use a sintaxe await e try...catch para lidar com erros de envio.

Observação

Os métodos de hub de chamada de um cliente só têm suporte ao usar o Serviço SignalR do Azure no modo Padrão. Para obter mais informações, confira Perguntas frequentes (repositório azure-signalr do GitHub).

Chamar métodos de cliente do hub

Defina os métodos que o hub chama usando connection.On após a criação, mas antes de iniciar a conexão.

connection.On<string, string>("ReceiveMessage", (user, message) =>
{
    this.Dispatcher.Invoke(() =>
    {
       var newMessage = $"{user}: {message}";
       messagesList.Items.Add(newMessage);
    });
});

O código anterior em connection.On é executado quando o código do lado do servidor o chama usando o método SendAsync.

public async Task SendMessage(string user, string message)
{
    await Clients.All.SendAsync("ReceiveMessage", user,message);
}

Observação

Embora o lado do hub da conexão dê suporte a mensagens fortemente tipadas, o cliente deve se registrar usando o método genérico HubConnection.On com o nome do método. Para obter um exemplo, confira Host ASP.NET Core SignalR em serviços em segundo plano.

Registro em log e tratamento de erros

Tratar erros com uma instrução try-catch. Inspecione o objeto Exception para determinar a ação adequada a ser tomada após a ocorrência de um erro.

try
{
    await connection.InvokeAsync("SendMessage", 
        userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{                
    messagesList.Items.Add(ex.Message);                
}

Recursos adicionais