Edit

ASP.NET Core SignalR .NET client

The ASP.NET Core SignalR .NET client library lets you communicate with SignalR hubs from .NET apps. This article describes how to use the APIs to connect to a SignalR hub, and call the .NET hub and client methods. The code sample in this article is a Windows Presentation Foundation (WPF) app that uses the ASP.NET Core SignalR .NET client.

View or download sample code (how to download)

Install the SignalR .NET client package

The Microsoft.AspNetCore.SignalR.Client package is required for .NET clients to connect to SignalR hubs. You can install the client library from the Visual Studio Package Manager Console or by using the .NET CLI.

Run the following command in the Package Manager Console window:

Install-Package Microsoft.AspNetCore.SignalR.Client

Connect to a hub

To establish a connection, create a HubConnectionBuilder and call Build. The hub URL, protocol, transport type, log level, headers, and other options can be configured while building a connection. Configure any required options by inserting any of the HubConnectionBuilder methods into Build. Start the connection with 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);                
            }
        }
    }
}

Handle lost connection

To reconnect with clients, you can set up automatic reconnection or configure the reconnection manually.

Automatically reconnect

The HubConnection can be configured to automatically reconnect by using the WithAutomaticReconnect method on the HubConnectionBuilder. It doesn't automatically reconnect by default.

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

Without any parameters, WithAutomaticReconnect() configures the client to wait 0, 2, 10, and 30 seconds respectively before trying each reconnect attempt. It stops after four failed attempts.

Before starting any reconnect attempts, the HubConnection transitions to the HubConnectionState.Reconnecting state and fires the Reconnecting event. This approach provides an opportunity to warn users that the connection is lost and to disable UI elements. Non-interactive apps can start queuing or dropping messages.

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

If the client successfully reconnects within its first four attempts, the HubConnection transitions back to the Connected state and fires the Reconnected event. This approach provides an opportunity to inform users the connection is now reestablished and to dequeue any queued messages.

Because the connection looks entirely new to the server, a new ConnectionId is provided to the Reconnected event handlers.

Warning

The Reconnected event handler's connectionId parameter is null if the HubConnection is configured to skip negotiation.

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() doesn't configure the HubConnection to retry initial start failures, so start failures need to be handled manually:

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

If the client doesn't successfully reconnect within its first four attempts, the HubConnection transitions to the Disconnected state and fires the Closed event. This approach provides an opportunity to attempt to restart the connection manually or inform users the connection is now permanently lost.

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

To configure a custom number of reconnect attempts before disconnecting or change the reconnect timing, WithAutomaticReconnect accepts an array of numbers representing the delay in milliseconds to wait before starting each reconnect attempt.

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.

The preceding example configures the HubConnection to start attempting reconnects immediately after the connection is lost. This approach is also true for the default configuration.

  • If the first reconnect attempt fails, the second reconnect attempt also starts immediately instead of waiting 2 seconds as defined in the default configuration.

  • If the second reconnect attempt fails, the third reconnect attempt starts in 10 seconds, which is the same behavior defined in the default configuration.

  • The custom behavior then diverges again from the default behavior by stopping after the third reconnect attempt failure. In the default configuration, one more reconnect attempt is made after another 30 seconds.

For more control over the timing and number of automatic reconnect attempts, WithAutomaticReconnect accepts an object implementing the IRetryPolicy interface, which has a single method named NextRetryDelay. NextRetryDelay takes a single argument with the type RetryContext. The RetryContext has three properties: PreviousRetryCount (type long), ElapsedTime (type TimeSpan), and RetryReason (type Exception).

  • Before the first reconnect attempt, PreviousRetryCount and ElapsedTime are both zero (0), and RetryReason is the Exception that caused the lost connection.

  • After each failed retry attempt, PreviousRetryCount increments by one, ElapsedTime updates to reflect the amount of time spent reconnecting so far, and RetryReason is the Exception that caused the last reconnect attempt to fail.

NextRetryDelay must return either a TimeSpan value that represents the time to wait before the next reconnect attempt or null if the HubConnection should stop reconnecting.

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

Alternatively, you can write code to reconnect your client manually, as demonstrated in the next section.

Manually reconnect

Warning

In versions earlier than 3.0, the .NET client for SignalR doesn't automatically reconnect. You must write code to reconnect your client manually.

Use the Closed event to respond to a lost connection. For example, you might want to automate reconnection.

The Closed event requires a delegate that returns a Task, which allows async code to run without using async void. To satisfy the delegate signature in a Closed event handler that runs synchronously, return Task.CompletedTask:

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

The main reason for the async support is so you can restart the connection. Starting a connection is an async action.

In a Closed handler that restarts the connection, consider waiting for some random delay to prevent overloading the server, as shown in the following example:

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

Call hub methods from client

InvokeAsync calls methods on the hub. Pass the hub method name and any arguments defined in the hub method to InvokeAsync. SignalR is asynchronous, so use async and await when making the calls.

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

The InvokeAsync method returns a Task that completes when the server method returns. The return value, if any, is provided as the result of the Task. Any exceptions thrown by the method on the server produce a faulted Task. Use await syntax to wait for the server method to complete and try...catch syntax to handle errors.

The SendAsync method returns a Task that completes when the message is sent to the server. No return value is provided because this Task doesn't wait until the server method completes. Any exceptions thrown on the client while sending the message produce a faulted Task. Use await and try...catch syntax to handle message send errors.

Note

Calling hub methods from a client is supported only when using the Azure SignalR Service in Default mode. For more information, see Frequently Asked Questions.

Call client methods from hub

Define the methods that the hub calls by using connection.On after building, but before starting the connection:

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

The preceding code in connection.On runs when server-side code calls it by using the SendAsync method:

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

Note

While the hub side of the connection supports strongly typed messaging, the client must register by using the generic method HubConnection.On with the method name. For an example, see Host ASP.NET Core SignalR in background services.

Error handling and logging

Handle errors with a try...catch statement. Inspect the Exception object to determine the proper action to take after an error occurs:

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