ASP.NET Core SignalR .NET クライアント

ASP.NET Core SignalR .NET クライアント ライブラリを使用すると、.NET アプリから SignalR ハブと通信できます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

この記事のコード サンプルは、ASP.NET Core SignalR .NET クライアントを使用する WPF アプリです。

SignalR.NET クライアント パッケージをインストールする

.NET クライアントが SignalR ハブに接続するには、Microsoft.AspNetCore.SignalR.Client パッケージが必要です。

クライアント ライブラリをインストールするには、[パッケージ マネージャー コンソール] ウィンドウで次のコマンドを実行します。

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

失われた接続を処理する

自動的に再接続する

HubConnection は、HubConnectionBuilderWithAutomaticReconnect メソッドを使用して自動的に再接続するように構成できます。 既定では、自動的に再接続されません。

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

パラメーターを指定しないで WithAutomaticReconnect() を使用すると、クライアントは、再接続の各試行の前に、それぞれ 0、2、10、30 秒間待機し、試行が 4 回失敗すると停止するように構成されます。

再接続を試み始める前に、HubConnectionHubConnectionState.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;
};

最初の 4 回の試行でクライアントが正常に再接続した場合、HubConnectionConnected 状態に戻り、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;
};

HubConnectionWithAutomaticReconnect() によって最初の開始失敗を再試行するように構成されないため、開始失敗は手動で処理する必要があります。

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

最初の 4 回の試行でクライアントが正常に再接続しなかった場合は、HubConnectionDisconnected 状態に遷移し、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 を構成しています。 これは、既定の構成でも同じです。

再接続の 1 回目の試行が失敗した場合は、既定の構成のように 2 秒待つことなく、2 回目の再接続の試行がすぐに開始されます。

再接続の 2 回目の試行が失敗した場合、3 回目の再接続の試行は 10 秒後に開始されます。これは、既定の構成と同様です。

その後のカスタム動作は、再び既定の動作とは異なり、3 回目の再接続の試行が失敗したら停止します。 既定の構成では、さらに 30 秒後にもう 1 回再接続が試みられます。

自動再接続試行のタイミングと回数をさらに細かく制御したい場合は、WithAutomaticReconnectIRetryPolicy インターフェイスが実装されているオブジェクトを受け取ります。このインターフェイスには、NextRetryDelay という名前のメソッドが 1 つ含まれます。

NextRetryDelay は、RetryContext 型の引数を 1 つ受け取ります。 RetryContext には 3 つのプロパティ PreviousRetryCountElapsedTimeRetryReason があり、それぞれ longTimeSpanException です。 最初の再接続試行の前は、PreviousRetryCountElapsedTime はどちらも 0 であり、RetryReason は接続が失われる原因となった Exception になっています。 再試行が失敗するたびに、PreviousRetryCount は 1 ずつインクリメントされ、ElapsedTime はそれまでの再接続に費やされた時間を反映するように更新され、RetryReason には最後の再接続試行が失敗した原因である Exception が設定されます。

NextRetryDelay では、次の再接続試行の前に待機する時間を表す TimeSpan を返すか、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 が生成されます。 送信エラーを処理するには、await および try...catch 構文を使います。

Note

クライアントからのハブのメソッドの呼び出しは、SignalRAzure Service を "既定" モードで使用している場合にのみサポートされます。 詳細については、「Frequently Asked Questions (よく寄せられる質問)」(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);
}

Note

接続のハブ側では厳密に型指定されたメッセージがサポートされますが、クライアントではジェネリック メソッド 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);                
}

その他のリソース