ASP.NET Core SignalR .NET クライアント ライブラリを使用すると、.NET アプリから SignalR ハブと通信できます。
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
この記事のコード サンプルは、ASP.NET Core SignalR .NET クライアントを使用する WPF アプリです。
SignalR.NET クライアント パッケージをインストールする
.NET クライアントが 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);
}
}
}
}
失われた接続を処理する
自動的に再接続する
HubConnection は、WithAutomaticReconnect
で HubConnectionBuilder メソッドを使用して自動的に再接続するように構成できます。 既定では、自動的に再接続されません。
HubConnection connection= new HubConnectionBuilder()
.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect()
.Build();
パラメーターを指定しないで WithAutomaticReconnect()
を使用すると、クライアントは、再接続の各試行の前に、それぞれ 0、2、10、30 秒間待機し、試行が 4 回失敗すると停止するように構成されます。
再接続を試み始める前に、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;
};
最初の 4 回の試行でクライアントが正常に再接続した場合、HubConnection
は Connected
状態に戻り、Reconnected
イベントを生成します。 これにより、接続が再確立されたことをユーザーに通知し、キューに格納されているメッセージをデキューできます。
サーバーからは接続はまったく新しいものと認識されるため、ConnectionId
イベント ハンドラーには新しい Reconnected
が提供されます。
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);
}
}
}
最初の 4 回の試行でクライアントが正常に再接続しなかった場合は、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
を構成しています。 これは、既定の構成でも同じです。
再接続の 1 回目の試行が失敗した場合は、既定の構成のように 2 秒待つことなく、2 回目の再接続の試行がすぐに開始されます。
再接続の 2 回目の試行が失敗した場合、3 回目の再接続の試行は 10 秒後に開始されます。これは、既定の構成と同様です。
その後のカスタム動作は、再び既定の動作とは異なり、3 回目の再接続の試行が失敗したら停止します。 既定の構成では、さらに 30 秒後にもう 1 回再接続が試みられます。
自動再接続試行のタイミングと回数をさらに細かく制御したい場合は、WithAutomaticReconnect
は IRetryPolicy
インターフェイスが実装されているオブジェクトを受け取ります。このインターフェイスには、NextRetryDelay
という名前のメソッドが 1 つ含まれます。
NextRetryDelay
は、RetryContext
型の引数を 1 つ受け取ります。
RetryContext
には 3 つのプロパティ PreviousRetryCount
、ElapsedTime
、RetryReason
があり、それぞれ long
、TimeSpan
、Exception
です。 最初の再接続試行の前は、PreviousRetryCount
と ElapsedTime
はどちらも 0 であり、RetryReason
は接続が失われる原因となった Exception になっています。 再試行が失敗するたびに、PreviousRetryCount
は 1 ずつインクリメントされ、ElapsedTime
はそれまでの再接続に費やされた時間を反映するように更新され、RetryReason
には最後の再接続試行が失敗した原因である Exception が設定されます。
NextRetryDelay
では、次の再接続試行の前に待機する時間を表す TimeSpan を返すか、null
が再接続を停止する必要がある場合は HubConnection
を返す必要があります。
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 は非同期なので、呼び出しを行うときに async
と await
を使います。
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
InvokeAsync
メソッドから返される Task
は、サーバー メソッドから戻ると完了します。 戻り値がある場合は、Task
の結果として提供されます。 サーバーのメソッドによって例外がスローされると、エラーのある Task
が生成されます。 サーバーのメソッドが完了するのを待機するには await
構文を使い、エラーを処理するには try...catch
を使います。
SendAsync
メソッドから返される Task
は、メッセージがサーバーに送信されると完了します。 この Task
はサーバーのメソッドが完了するまで待機しないので、戻り値は提供されません。 メッセージの送信中にクライアントで例外がスローされると、エラーのある Task
が生成されます。 送信エラーを処理するには、await
および try...catch
構文を使います。
注
クライアントからのハブのメソッドの呼び出しは、SignalRAzure Service を "既定" モードで使用している場合にのみサポートされます。 詳細については、「 頻度の低い質問」を参照してください。
ハブからクライアントのメソッドを呼び出す
ハブが呼び出すメソッドの定義は、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);
}
その他のリソース
ASP.NET Core