ASP.NET Core SignalR .Net 客户端ASP.NET Core SignalR .NET Client

通过 ASP.NET Core SignalR .net 客户端库,你可以 SignalR 从 .net 应用程序与中心进行通信。The ASP.NET Core SignalR .NET client library lets you communicate with SignalR hubs from .NET apps.

查看或下载示例代码如何下载View or download sample code (how to download)

本文中的代码示例是使用 .Net 客户端 ASP.NET Core 的 WPF 应用程序 SignalR 。The code sample in this article is a WPF app that uses the ASP.NET Core SignalR .NET client.

安装 SignalR .net 客户端包Install the SignalR .NET client package

AspNetCore. SignalR若要连接到集线器,.net 客户端需要客户端包 SignalR 。The Microsoft.AspNetCore.SignalR.Client package is required for .NET clients to connect to SignalR hubs.

若要安装客户端库,请在 "包管理器控制台" 窗口中运行以下命令:To install the client library, run the following command in the Package Manager Console window:

Install-Package Microsoft.AspNetCore.SignalR.Client

连接到集线器Connect to a hub

若要建立连接,请创建 HubConnectionBuilder 并调用 BuildTo establish a connection, create a HubConnectionBuilder and call Build. 在建立连接时,可以配置中心 URL、协议、传输类型、日志级别、标头和其他选项。The hub URL, protocol, transport type, log level, headers, and other options can be configured while building a connection. 通过在中插入任意方法来配置任何所需的选项 HubConnectionBuilder BuildConfigure any required options by inserting any of the HubConnectionBuilder methods into Build. 启动与的连接 StartAsyncStart 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

自动重新连接Automatically reconnect

HubConnection可以将配置为使用上的方法自动重新连接 WithAutomaticReconnect HubConnectionBuilderThe HubConnection can be configured to automatically reconnect using the WithAutomaticReconnect method on the HubConnectionBuilder. 默认情况下,它不会自动重新连接。It won't automatically reconnect by default.

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

在没有任何参数的情况下,会 WithAutomaticReconnect() 将客户端配置为分别等待0、2、10和30秒,然后再尝试重新连接尝试。Without any parameters, WithAutomaticReconnect() configures the client to wait 0, 2, 10, and 30 seconds respectively before trying each reconnect attempt, stopping after four failed attempts.

在开始任何重新连接尝试之前, HubConnection 将转换为 HubConnectionState.Reconnecting 状态,并触发 Reconnecting 事件。Before starting any reconnect attempts, the HubConnection will transition to the HubConnectionState.Reconnecting state and fire the Reconnecting event. 这为用户提供警告连接已丢失并禁用 UI 元素的机会。This provides an opportunity to warn users that the connection has been 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;
};

如果客户端在其前四次尝试内成功重新连接,则 HubConnection 将转换回 Connected 状态并激发 Reconnected 事件。If the client successfully reconnects within its first four attempts, the HubConnection will transition back to the Connected state and fire the Reconnected event. 这为用户提供了通知用户已重新建立连接并取消排队消息的排队的机会。This provides an opportunity to inform users the connection has been reestablished and dequeue any queued messages.

由于连接在服务器上看起来是全新的,因此 ConnectionId 将向事件处理程序提供一个新的 ReconnectedSince the connection looks entirely new to the server, a new ConnectionId will be provided to the Reconnected event handlers.

警告

Reconnected connectionId 如果 HubConnection 配置为跳过协商,事件处理程序的参数将为 null。The Reconnected event handler's connectionId parameter will be null if the HubConnection was 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()不会将配置 HubConnection 为重试初始启动失败,因此,需要手动处理启动失败:WithAutomaticReconnect() won'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);
        }
    }
}

如果客户端在其前四次尝试中未成功重新连接,则 HubConnection 将转换为 Disconnected 状态并触发 Closed 事件。If the client doesn't successfully reconnect within its first four attempts, the HubConnection will transition to the Disconnected state and fire the Closed event. 这为尝试手动重新启动连接或通知用户连接永久丢失有机会。This provides an opportunity to attempt to restart the connection manually or inform users the connection has been 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;
};

若要在断开连接或更改重新连接时间安排之前配置自定义的重新连接尝试次数,请 WithAutomaticReconnect 接受一个数字数组,表示在开始每次重新连接尝试之前等待的延迟(以毫秒为单位)。In order 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.

前面的示例将配置 HubConnection 为在连接丢失后立即开始尝试重新连接。The preceding example configures the HubConnection to start attempting reconnects immediately after the connection is lost. 这也适用于默认配置。This is also true for the default configuration.

如果第一次重新连接尝试失败,则第二次重新连接尝试还会立即启动,而不是等待2秒,就像在默认配置中一样。If the first reconnect attempt fails, the second reconnect attempt will also start immediately instead of waiting 2 seconds like it would in the default configuration.

如果第二次重新连接尝试失败,则第三次重新连接尝试将在10秒内启动,这与默认配置相同。If the second reconnect attempt fails, the third reconnect attempt will start in 10 seconds which is again like the default configuration.

然后,在第三次重新连接尝试失败后,自定义行为将再次从默认行为与其分离。The custom behavior then diverges again from the default behavior by stopping after the third reconnect attempt failure. 在默认配置中,将在另一个30秒内再次尝试重新连接。In the default configuration there would be one more reconnect attempt in another 30 seconds.

如果需要更好地控制计时和自动重新连接尝试的次数,则 WithAutomaticReconnect 接受一个实现接口的 IRetryPolicy 对象,该对象具有一个名为的方法 NextRetryDelayIf you want even 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采用类型为的单个自变量 RetryContextNextRetryDelay takes a single argument with the type RetryContext. RetryContext有三个属性: PreviousRetryCountElapsedTimeRetryReasonlong 分别为、和 TimeSpan ExceptionThe RetryContext has three properties: PreviousRetryCount, ElapsedTime and RetryReason, which are a long, a TimeSpan and an Exception respectively. 第一次重新连接尝试之前, PreviousRetryCount 和均 ElapsedTime 为零, RetryReason 将是导致连接丢失的异常。Before the first reconnect attempt, both PreviousRetryCount and ElapsedTime will be zero, and the RetryReason will be the Exception that caused the connection to be lost. 每次失败的重试次数递增一次后,将 PreviousRetryCount ElapsedTime 更新以反映到目前为止所用的时间, RetryReason 这是导致上次重新连接尝试失败的异常。After each failed retry attempt, PreviousRetryCount will be incremented by one, ElapsedTime will be updated to reflect the amount of time spent reconnecting so far, and the RetryReason will be the Exception that caused the last reconnect attempt to fail.

NextRetryDelay必须返回一个 TimeSpan,表示在下一次重新连接尝试之前要等待的时间,或者 null ,如果应停止重新连接,则为 HubConnectionNextRetryDelay must return either a TimeSpan representing 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 that will reconnect your client manually as demonstrated in Manually reconnect.

手动重新连接Manually reconnect

警告

在3.0 之前,.NET 客户端 SignalR 不会自动重新连接。Prior to 3.0, the .NET client for SignalR doesn't automatically reconnect. 必须编写代码来手动重新连接客户端。You must write code that will reconnect your client manually.

使用 Closed 事件响应断开连接。Use the Closed event to respond to a lost connection. 例如,你可能需要自动重新连接。For example, you might want to automate reconnection.

Closed事件需要一个返回的委托 Task ,该委托允许异步代码在不使用的情况下运行 async voidThe Closed event requires a delegate that returns a Task, which allows async code to run without using async void. 若要在 Closed 同步运行的事件处理程序中满足委托签名,请返回 Task.CompletedTaskTo 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.

Closed 重启连接的处理程序中,考虑等待一些随机延迟以防止服务器过载,如以下示例中所示: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在集线器上调用方法。InvokeAsync calls methods on the hub. 将 hub 方法名称和在 hub 方法中定义的任何自变量传递给 InvokeAsyncPass the hub method name and any arguments defined in the hub method to InvokeAsync. SignalR是异步的,因此 asyncawait 进行调用时使用和。SignalR is asynchronous, so use async and await when making the calls.

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

InvokeAsync方法返回一个在 Task 服务器方法返回时完成的。The InvokeAsync method returns a Task which completes when the server method returns. 返回值(如果有)作为的结果提供 TaskThe return value, if any, is provided as the result of the Task. 服务器上的方法所引发的任何异常都将导致出错 TaskAny exceptions thrown by the method on the server produce a faulted Task. 使用 await 语法等待服务器方法完成,并使用 try...catch 语法来处理错误。Use await syntax to wait for the server method to complete and try...catch syntax to handle errors.

SendAsync方法返回一个,该方法在 Task 消息已发送到服务器时完成。The SendAsync method returns a Task which completes when the message has been sent to the server. 不会提供返回值,因为它 Task 不会等到服务器方法完成。No return value is provided since this Task doesn't wait until the server method completes. 发送消息时,在客户端上引发的任何异常都会产生出错 TaskAny exceptions thrown on the client while sending the message produce a faulted Task. 使用 awaittry...catch 语法处理发送错误。Use await and try...catch syntax to handle send errors.

备注

如果 SignalR 在无服务器模式下使用 Azure 服务,则无法从客户端调用集线器方法。If you're using Azure SignalR Service in Serverless mode, you cannot call hub methods from a client. 有关详细信息,请参阅 SignalR 服务文档For more information, see the SignalR Service documentation.

从中心调用客户端方法Call client methods from hub

定义集线器在 connection.On 生成之后、启动连接之前调用的方法。Define methods the hub calls 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);
    });
});

connection.On当服务器端代码使用方法调用时,上面的代码将运行 SendAsyncThe preceding code in connection.On runs when server-side code calls it using the SendAsync method.

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

错误处理和日志记录Error handling and logging

使用 try-catch 语句处理错误。Handle errors with a try-catch statement. 检查 Exception 对象,确定发生错误后要采取的适当操作。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);                
}

其他资源Additional resources