ASP.NET Core SignalR .NET 用戶端

ASP.NET Core SignalR .NET 用戶端程式庫可讓您從 .NET 應用程式與 SignalR 中樞通訊。

檢視或下載範例程式碼 \(英文\) (如何下載)

本文中的程式碼範例是使用 ASP.NET Core SignalR .NET 用戶端的 WPF 應用程式。

安裝 SignalR .NET 用戶端套件

.NET 用戶端需要Microsoft.AspNetCore.SignalR.Client 套件才能連線到 SignalR 中樞。

若要安裝用戶端程式庫,請在 [套件管理員主控台] 視窗中執行下列命令:

Install-Package Microsoft.AspNetCore.SignalR.Client

連線到中樞

若要建立連接,請建立 HubConnectionBuilder 並呼叫 Build。 您可以在建置連線時設定中樞 URL、通訊協定、傳輸類型、記錄層級、標頭和其他選項。 將任何 HubConnectionBuilder 方法插入 至 Build,以設定任何必要選項。 使用 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);                
            }
        }
    }
}

處理遺失的連線

自動重新連線

透過使用 HubConnectionBuilder 上的 WithAutomaticReconnect 方法,可以將 HubConnection 設定為自動重新連線。 依預設,並不會自動重新連線。

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

如果不使用任何參數,WithAutomaticReconnect() 將用戶端設定為在嘗試每次重新連線之前分別等待 0、2、10 和 30 秒,並在四次嘗試失敗後停止。

開始任何重新連線嘗試之前,HubConnection 轉換至 HubConnectionState.Reconnecting 狀態並引發 Reconnecting 事件。 這可讓您警告使用者連線已遺失,並停用 UI 元素。 非互動式應用程式可以開始佇列或卸除訊息。

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 事件。 這可讓您通知使用者連線已重新建立,並清除任何已排入佇列的訊息。

由於該連線對伺服器來說是全新的,因此將向 Reconnected 事件處理常式提供新的 ConnectionId

警告

如果 HubConnection 設定為 跳過交涉Reconnected 事件處理常式的 connectionId 參數將會是 Null。

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 設定為重試初始啟動失敗,因此必須手動處理啟動失敗:

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 事件。 這可讓您嘗試手動重新啟動連線,或通知使用者連線已永久遺失。

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 接受一個數字陣列,表示在開始每次重新連線嘗試之前要等待的延遲時間 (以毫秒為單位)。

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 設定為在連線遺失後立即開始嘗試重新連線。 對於預設組態也會這樣做。

如果第一次重新連線嘗試失敗,第二次重新連線嘗試也會立即啟動,而不是像預設組態中那樣等待 2 秒。

如果第二次重新連線嘗試失敗,第三次重新連線嘗試將會在 10 秒內開始,這又與預設組態類似。

然後,自訂行為會在第三次重新連線嘗試失敗後停止,再次偏離預設行為。 在預設組態中,會再嘗試一次 30 秒的重新連線嘗試。

如果您想要更充分掌控自動重新連線嘗試的時間和次數,WithAutomaticReconnect 接受一個實作 IRetryPolicy 介面的物件,該介面有一個名為 NextRetryDelay 的方法。

NextRetryDelay 會採用類型為 RetryContext 的單一引數。 RetryContext 有三個屬性:PreviousRetryCountElapsedTimeRetryReason,分別是 longTimeSpanException。 在第一次重新連線嘗試之前,PreviousRetryCountElapsedTime 都會是零,而 RetryReason 會是造成連線中斷的例外狀況。 每次重試失敗之後,PreviousRetryCount 將會遞增一次,ElapsedTime 將會更新以反映到目前為止重新連線所花費的時間量,而 RetryReason 會是造成上次重新連線嘗試失敗的例外狀況。

NextRetryDelay 必須傳回一個時間範圍,代表下一次重新連線嘗試之前所要等候的時間,或者,如果 HubConnection 應該停止重新連線,則傳回 null

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

或者,您可以撰寫程式碼,以手動方式重新連線用戶端,如手動重新連線中所示。

手動重新連線

警告

在 3.0 之前,SignalR 的 .NET 用戶端不會自動重新連線。 您必須撰寫程式碼,以手動方式重新連線您的用戶端。

使用 Closed 事件來回應連線中斷。 例如,您可能想要自動重新連線。

Closed 事件需要傳回 Task 的委派,其可讓非同步程式碼執行而不使用 async void。 若要滿足以同步方式執行的 Closed 事件處理常式中的委派簽章,請傳回 Task.CompletedTask

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

非同步支援的主要原因是您可以重新啟動連線。 啟動連線是一個非同步動作。

在重新啟動連線的 Closed 處理常式中,請考慮等候一些任意的延遲以防止伺服器多載,如下列範例所示:

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

從用戶端呼叫中樞方法

InvokeAsync 會呼叫中樞上的方法。 將中樞方法名稱和中樞方法中定義的任何引數傳遞至 InvokeAsync。 SignalR 是非同步,因此請在進行呼叫時使用 asyncawait

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

方法 InvokeAsync 會傳回 Task,其會在伺服器方法傳回時完成。 如果有任何傳回值,則會以 Task 的結果提供。 方法在伺服器上擲回的任何例外狀況都會產生錯誤 Task。 使用 await 語法等候伺服器方法完成,並使用 try...catch 語法處理錯誤。

方法 SendAsync 會傳回 Task,其會在訊息傳送至伺服器時完成。 未提供傳回值,因為此 Task 不會等到伺服器方法完成。 傳送訊息時,用戶端上擲回的任何例外狀況都會產生錯誤的 Task。 使用 awaittry...catch 語法來處理傳送錯誤。

注意

只有在預設模式下使用 Azure SignalR 服務時,才支援從用戶端呼叫中樞方法。 如需詳細資訊,請參閱常見問題 (azure-signalr GitHub 存放庫)。

從中樞呼叫用戶端方法

定義中樞在建置之後但在啟動連線之前使用 connection.On 呼叫的方法。

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

connection.On 中的上述程式碼會在伺服器端程式碼使用 SendAsync 方法呼叫時執行。

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

注意

雖然連線的中樞端支援強型別傳訊,但用戶端必須使用具有方法名稱的泛型方法 HubConnection.On 進行註冊。 如需範例,請參閱背景服務中的主機 ASP.NET Core SignalR

錯誤處理和記錄

使用 try-catch 陳述式處理錯誤。 檢查 Exception 物件,以判斷錯誤發生後要採取的適當動作。

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

其他資源