ASP.NET Core BlazorSignalR ガイダンス
Note
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポートされなくなりました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、Blazor アプリで SignalR 接続を構成および管理する方法について説明します。
ASP.NET Core SignalR の構成に関する一般的なガイダンスについては、ドキュメントの ASP.NET Core SignalR の概要に関するトピック (特に、ASP.NET Core SignalR の構成) をご覧ください。
サーバー側のアプリは、ASP.NET Core SignalR を使用して、ブラウザーと通信します。 SignalR のホストとスケーリングの条件 は、サーバー側のアプリに適用されます。
Blazor は、待ち時間、信頼性、およびセキュリティが低いために WebSocket を SignalR トランスポートとして使用する場合に最適です。 WebSocket が使用できない場合や、ロング ポーリングを使用するようにアプリが明示的に構成されている場合は、SignalR によってロング ポーリングが使用されます。
ステートフル再接続がある Azure SignalR サービス
Azure
対話型サーバー コンポーネントの WebSocket 圧縮
既定では、対話型サーバー コンポーネントは以下を実行します。
WebSocket 接続に対して圧縮を有効にします。 DisableWebSocketCompression (既定値:
false
) は WebSocket の圧縮を制御します。'self'
に設定されたframe-ancestors
Content Security Policy (CSP) ディレクティブを採用します。これは、圧縮が有効になっているか、WebSocket コンテキストの構成が提供される場合にだけ、アプリをそのアプリの提供元の<iframe>
に埋め込むことを許可します。ContentSecurityFrameAncestorPolicy
はframe-ancestors
CSP を制御します。
ContentSecurityFrameAncestorsPolicy の値を null
に設定すると、frame-ancestors
CSP を手動で削除できます。これは、一元的な方法で CSP を構成する場合があるためです。 frame-ancestors
CSP を一元的に管理する場合は、最初のドキュメントがレンダリングされるたびにポリシーを適用するようにする必要があります。 アプリが攻撃に対して脆弱になる可能性があるため、ポリシーを完全に削除することは推奨されません。
ConfigureWebSocketAcceptContext を使用して、サーバー コンポーネントが使用する Websocket 接続に対して WebSocketAcceptContext を構成します。 既定では、圧縮を有効にし、ContentSecurityFrameAncestorsPolicy で定義されている frame-ancestors の CSP を設定するポリシーが適用されます。
使用例:
DisableWebSocketCompression を true
に設定することで圧縮を無効にします。これにより、アプリの攻撃に対する脆弱性は減少しますが、パフォーマンスが低下する可能性があります。
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.DisableWebSocketCompression = true)
圧縮が有効になっている場合は、'none'
という値 (単一引用符が必要) を使用してより厳密な frame-ancestors
CSP を構成します。これにより、WebSocket 圧縮は実行できますが、ブラウザーはアプリを <iframe>
に埋め込むことができなくなります。
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")
圧縮が有効になっている場合は、ContentSecurityFrameAncestorsPolicy を null
に設定することで frame-ancestors
CSP を削除します。 このシナリオが推奨されるのは、一元化された方法で CSP を設定するアプリに対してだけです。
builder.MapRazorComponents<App>()
.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)
重要
ブラウザーは、最も厳密なポリシー ディレクティブ値を使用して、複数の CSP ヘッダーの CSP ディレクティブを適用します。 したがって、開発者は意図的または誤って 'self'
より弱い frame-ancestors
ポリシーを追加することができません。
ContentSecurityFrameAncestorsPolicy に渡される文字列値には、単一引用符が必要です。
サポートされない値: none
、self
サポートされている値: 'none'
、'self'
追加のオプションには、1 つ以上のホスト ソースとスキーム ソースの指定が含まれます。
セキュリティへの影響については、「ASP.NET Core 対話型サーバー側レンダリングBlazorに関する脅威の軽減策についてのガイダンス」を参照してください。 frame-ancestors
ディレクティブの詳細については、「CSP: frame-ancestors
(MDN ドキュメント)」を参照してください。
ホット リロードの応答圧縮を無効にする
ホット リロードを使用する場合は、Development
環境の応答圧縮ミドルウェアを無効にします。 プロジェクト テンプレートからの既定のコードを使用するかどうかに関係なく、要求処理パイプラインで常に最初に UseResponseCompression を呼び出します。
Program
ファイルで次の操作を行います。
if (!app.Environment.IsDevelopment())
{
app.UseResponseCompression();
}
クライアント側の認証用 SignalR クロスオリジン ネゴシエーション
このセクションでは、Cookie や HTTP 認証ヘッダーなどの資格情報を送信するように SignalR の基となるクライアントを構成する方法について説明します。
SetBrowserRequestCredentials を使用して、クロスオリジン fetch
要求に Include を設定します。
IncludeRequestCredentialsMessageHandler.cs
:
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;
public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return base.SendAsync(request, cancellationToken);
}
}
ハブ接続が構築されている場合は、HttpMessageHandler を HttpMessageHandlerFactory オプションに割り当てます。
private HubConnectionBuilder? hubConnection;
...
hubConnection = new HubConnectionBuilder()
.WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
{
options.HttpMessageHandlerFactory = innerHandler =>
new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
}).Build();
前の例では、ハブ接続 URL を /chathub
の絶対 URI アドレスに構成しています。 URI は、文字列 (https://signalr.example.com
など) または構成を使用して設定することもできます。 Navigation
は挿入された NavigationManager です。
詳しくは、「ASP.NET Core SignalR の構成」をご覧ください。
クライアント側レンダリング
プリレンダリングが構成されている場合、サーバーへのクライアント接続が確立される前に、プリレンダリングが行われます。 詳細については、「ASP.NET Core コンポーネントRazorをプリレンダリングする」を参照してください。
プリレンダリングが構成されている場合、サーバーへのクライアント接続が確立される前に、プリレンダリングが行われます。 詳細については、次の記事を参照してください。
プリレンダリングされた状態サイズと SignalR メッセージ サイズの制限
大規模なプリレンダリングされた状態サイズが Blazor の SignalR 回線メッセージ サイズの制限を超える可能性があり、その結果、次のようになります。
- この SignalR 回線は、クライアントで次のエラーで初期化に失敗します: Circuit host not initialized.
- 回線が失敗すると、クライアントの再接続 UI が表示されます。 復旧はできません。
この問題を解決するには、次の "いずれかの" 方法を使用します。
- プリレンダリングされた状態に入れるデータの量を減らします。
- SignalR メッセージ サイズの制限を増やします。 警告: 制限を大きくすると、サービス拒否 (DoS) 攻撃のリスクが高まる可能性があります。
その他のクライアント側リソース
- SignalR ハブをセキュリティで保護する
- ASP.NET Core の概要SignalR
- ASP.NET Core SignalR の構成
- Blazor サンプル GitHub リポジトリ (
dotnet/blazor-samples
) (ダウンロード方法)
サーバー側の webfarm ホスティングにセッション アフィニティ (スティッキー セッション) を使用する
複数のバックエンド サーバーが使用されている場合、アプリに、スティッキー セッションとも呼ばれる、セッション アフィニティを実装する必要があります。 セッション アフィニティにより、接続が切断された場合にクライアントの回線が同じサーバーに再接続されることが保証されます。これは、クライアントの状態が、最初にクライアントの回線を確立したサーバーのメモリにのみ保持されるため重要です。
Webfarm で、セッション アフィニティを有効にしていないアプリからは次のエラーがスローされます。
Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed.
Azure App Service ホスティングがあるセッション アフィニティの詳細については、「ASP.NET Core サーバー側の Blazor アプリをホストしてデプロイする」を参照してください。
Azure SignalR Service
任意の Azure SignalR Service は、アプリの SignalR ハブと連携して、サーバー側のアプリを大規模なコンカレント接続にスケールアップします。 さらに、サービスのグローバル リーチとハイパフォーマンスのデータ センターは、地理的条件による待機時間の短縮に役立ちます。
このサービスは、Azure App Service または Azure Container Apps でホストされている Blazor アプリには必要ありませんが、他のホスティング環境で役立つ場合があります。
- 接続のスケールアウトを容易にするには。
- グローバル分散を行う。
詳細については、「ASP.NET Core サーバー側 Blazor アプリをホストしてデプロイする」を参照してください。
サーバー側の回線ハンドラーのオプション
CircuitOptions を使用して回線を構成します。 参照ソースの既定値を表示します。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
オプションの AddInteractiveServerComponents へのデリゲートを使用して、Program
ファイルのオプションを読み込みまたは設定します。 {OPTION}
プレースホルダーがオプションを表し、{VALUE}
プレースホルダーが値示しています。
Program
ファイルで次の操作を行います。
builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
options.{OPTION} = {VALUE};
});
オプションの AddServerSideBlazor へのデリゲートを使用して、Program
ファイルのオプションを読み込みまたは設定します。 {OPTION}
プレースホルダーがオプションを表し、{VALUE}
プレースホルダーが値示しています。
Program
ファイルで次の操作を行います。
builder.Services.AddServerSideBlazor(options =>
{
options.{OPTION} = {VALUE};
});
オプションの AddServerSideBlazor へのデリゲートを使用して、Startup.ConfigureServices
ファイルのオプションを読み込みまたは設定します。 {OPTION}
プレースホルダーがオプションを表し、{VALUE}
プレースホルダーが値示しています。
Startup.cs
の Startup.ConfigureServices
で:
services.AddServerSideBlazor(options =>
{
options.{OPTION} = {VALUE};
});
HubConnectionContext を構成するには、HubConnectionContextOptions と共に AddHubOptions を使用します。 参照ソースのハブ接続コンテキスト オプションの既定値を表示します。 SignalR ドキュメントのオプションの説明については、「ASP.NET Core SignalR の構成」を参照してください。 {OPTION}
プレースホルダーがオプションを表し、{VALUE}
プレースホルダーが値示しています。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
Program
ファイルで次の操作を行います。
builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
Program
ファイルで次の操作を行います。
builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
Startup.cs
の Startup.ConfigureServices
で:
services.AddServerSideBlazor().AddHubOptions(options =>
{
options.{OPTION} = {VALUE};
});
警告
MaximumReceiveMessageSize の既定値は 32 KB です。 この値を大きくすると、サービス拒否 (DoS) 攻撃のリスクが高まるおそれがあります。
Blazor は既定値である 1 に設定された MaximumParallelInvocationsPerClient に依存します。 詳細については、「MaximumParallelInvocationsPerClient > 1 によって Blazor Server モードでのファイル アップロードが中断される (dotnet/aspnetcore
#53951)」を参照してください。
メモリ管理の詳細については、「ASP.NET Core サーバー側 Blazor アプリをホストしてデプロイする」を参照してください。
Blazor ハブのオプション
MapBlazorHub オプションを構成して、Blazor ハブの HttpConnectionDispatcherOptions を制御します。 参照ソースのハブ接続ディスパッチャー オプションの既定値を表示します。 {OPTION}
プレースホルダーがオプションを表し、{VALUE}
プレースホルダーが値示しています。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
アプリの Program
ファイル内で app.MapRazorComponents
呼び出しの後に app.MapBlazorHub
呼び出しを配置します。
app.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
MapBlazorHub で AddInteractiveServerRenderMode が使用するハブの構成は、AmbiguousMatchException
で失敗します。
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints.
.NET 8 を対象とするアプリの問題を回避するには、WithOrder メソッドを使用すると、カスタム構成 Blazor ハブの優先順位を高くします。
app.MapBlazorHub(options =>
{
options.CloseOnAuthenticationExpiration = true;
}).WithOrder(-1);
詳細については、次のリソースを参照してください。
アプリ Program
のファイルに app.MapBlazorHub
へのオプションを提供します。
app.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
エンドポイント ルーティング構成に app.MapBlazorHub
へのオプションを提供します。
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub(options =>
{
options.{OPTION} = {VALUE};
});
...
});
最大受信メッセージ サイズ
このセクションは、SignalR を実装するプロジェクトにのみ適用されます。
ハブ メソッドで許可されている SignalR の最大受信メッセージ サイズは、HubOptions.MaximumReceiveMessageSize によって制限されます (既定値: 32 KB)。 SignalR のメッセージが MaximumReceiveMessageSize より大きい場合は、エラーがスローされます。 このフレームワークでは、ハブからクライアントへの SignalR メッセージのサイズが制限されることはありません。
SignalR のログがDebug または Trace に設定されていない場合、メッセージ サイズのエラーはブラウザーの開発者ツール コンソールにのみ表示されます。
エラー :次のエラーで接続が切断されました。"エラー: サーバーが終了時にエラーを返しました:接続はエラーで終了しました。"
SignalR サーバー側のログが Debug または Trace に設定されている場合、サーバー側のログには、メッセージ サイズ エラーの InvalidDataException が表示されます。
appsettings.Development.json
:
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
...
"Microsoft.AspNetCore.SignalR": "Debug"
}
}
}
エラー:
System.IO.InvalidDataException:メッセージの最大サイズ 32,768 B を超えました。 メッセージのサイズは、AddHubOptions で構成できます。
1 つの方法として、Program
ファイルで MaximumReceiveMessageSize を設定して制限を引き上げます。 次の例では、受信メッセージの最大サイズを 64 KB に設定します。
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
SignalR の受信メッセージのサイズ制限を増やすと、サーバー リソースを増やす必要があり、サービス拒否 (DoS) 攻撃のリスクが高くなります。 また、大量のコンテンツを文字列またはバイト配列としてメモリに読み取ると、ガベージ コレクターがうまく機能しない割り当てが発生する可能性もあり、その結果、パフォーマンスがさらに低下します。
大きなペイロードを読み取るためより良い選択肢は、小さいチャンクでコンテンツを送信し、ペイロードを Stream として処理することです。 これは、大量の JavaScript (JS) 相互運用機能 JSON ペイロードを読み取る場合、または JS 相互運用機能データを JavaScript で生バイトとして利用できる場合に使用できます。 InputFile
コンポーネントに似た手法を使ってサーバー側アプリで大規模なバイナリ ペイロードを送信する方法の例については、Binary Submit サンプル アプリと BlazorInputLargeTextArea
コンポーネント サンプルを参照してください。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
SignalR で大きなペイロードを処理するフォームでは、ストリーミング JS 相互運用を直接使用することもできます。 詳細については、「ASP.NET Core Blazor で JavaScript 関数から .NET メソッドを呼び出す」を参照してください。 <textarea>
データをサーバーにストリーミングするフォームの例については、「ASP.NET Core Blazor のフォームのトラブルシューティング」を参照してください。
1 つの方法として、Program
ファイルで MaximumReceiveMessageSize を設定して制限を引き上げます。 次の例では、受信メッセージの最大サイズを 64 KB に設定します。
builder.Services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
SignalR の受信メッセージのサイズ制限を増やすと、サーバー リソースを増やす必要があり、サービス拒否 (DoS) 攻撃のリスクが高くなります。 また、大量のコンテンツを文字列またはバイト配列としてメモリに読み取ると、ガベージ コレクターがうまく機能しない割り当てが発生する可能性もあり、その結果、パフォーマンスがさらに低下します。
大きなペイロードを読み取るためより良い選択肢は、小さいチャンクでコンテンツを送信し、ペイロードを Stream として処理することです。 これは、大量の JavaScript (JS) 相互運用機能 JSON ペイロードを読み取る場合、または JS 相互運用機能データを JavaScript で生バイトとして利用できる場合に使用できます。 Blazor Server で大規模なバイナリ ペイロードを送信する、InputFile
コンポーネントに似た手法を使用する方法の例については、Binary Submit サンプル アプリおよびBlazorInputLargeTextArea
コンポーネント サンプルを参照してください。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
SignalR で大きなペイロードを処理するフォームでは、ストリーミング JS 相互運用を直接使用することもできます。 詳細については、「ASP.NET Core Blazor で JavaScript 関数から .NET メソッドを呼び出す」を参照してください。 Blazor Server アプリで <textarea>
データをストリーミングするフォームの例については、「ASP.NET Core Blazorフォームのトラブルシューティング」を参照してください。
制限値を増やすには、Startup.ConfigureServices
で MaximumReceiveMessageSize を設定します。
services.AddServerSideBlazor()
.AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);
SignalR の受信メッセージのサイズ制限を増やすと、サーバー リソースを増やす必要があり、サービス拒否 (DoS) 攻撃のリスクが高くなります。 また、大量のコンテンツを文字列またはバイト配列としてメモリに読み取ると、ガベージ コレクターがうまく機能しない割り当てが発生する可能性もあり、その結果、パフォーマンスがさらに低下します。
大量のデータを転送するコードを開発するときは、次のガイダンスを考慮してください。
- SignalR の受信メッセージ サイズの制限を超えるデータを転送するには、ネイティブ ストリーミング JS 相互運用サポートを利用します。
- 一般的なヒント:
- JS および C# コードで大きなオブジェクトを割り当てないでください。
- プロセスの完了時またはキャンセル時に、消費していたメモリを解放します。
- セキュリティ上の理由から、次の追加要件を適用します。
- 渡すことのできるファイルまたはデータの最大サイズを宣言します。
- クライアントからサーバーへの最小アップロード レートを宣言します。
- データがサーバーによって受信されたら、データは:
- すべてのセグメントが収集されるまで、一時的にメモリ バッファーに格納できます。
- 直ちに消費できます。 たとえば、データは、データベースに直ちに格納することも、セグメントを受信するたびにディスクに書き込むこともできます。
- データをより小さな部分にスライスし、すべてのデータがサーバーによって受信されるまでデータ セグメントを順番に送信します。
- JS および C# コードで大きなオブジェクトを割り当てないでください。
- データを送受信するときに、メイン UI スレッドを長時間ブロックしないでください。
- プロセスの完了時またはキャンセル時に、消費していたメモリを解放します。
- セキュリティ上の理由から、次の追加要件を適用します。
- 渡すことのできるファイルまたはデータの最大サイズを宣言します。
- クライアントからサーバーへの最小アップロード レートを宣言します。
- データがサーバーによって受信されたら、データは:
- すべてのセグメントが収集されるまで、一時的にメモリ バッファーに格納できます。
- 直ちに消費できます。 たとえば、データは、データベースに直ちに格納することも、セグメントを受信するたびにディスクに書き込むこともできます。
Blazor サーバー側ハブのエンドポイント ルート構成
Program
ファイルで MapBlazorHub を呼び出して、BlazorHub をアプリの既定のパスにマップします。 Blazor スクリプト (blazor.*.js
) は、MapBlazorHub によって作成されたエンドポイントを自動的に指します。
UI にサーバー側の接続状態を反映する
サーバーへの接続が失われたことがクライアントで検出されると、クライアントによって再接続が試行される間、ユーザーに対して既定の UI が表示されます。
再接続に失敗した場合、ユーザーはページを再試行または再読み込みするように指示されます。
再接続に成功すると、多くの場合、ユーザーの状態が失われます。 カスタム コードを任意のコンポーネントに追加して、接続エラーの間にユーザーの状態を保存および再読み込みできます。 詳細については、「ASP.NET Core Blazor 状態管理」を参照してください。
UI をカスタマイズするには、<body>
要素コンテンツの components-reconnect-modal
の id
を持つ 1 つの要素を定義します。 次の例では、App
コンポーネントに要素を配置します。
App.razor
:
UI をカスタマイズするには、<body>
要素コンテンツの components-reconnect-modal
の id
を持つ 1 つの要素を定義します。 次の例では、ホスト ページに要素を配置します。
Pages/_Host.cshtml
:
UI をカスタマイズするには、<body>
要素コンテンツの components-reconnect-modal
の id
を持つ 1 つの要素を定義します。 次の例では、レイアウト ページに要素を配置します。
Pages/_Layout.cshtml
:
UI をカスタマイズするには、<body>
要素コンテンツの components-reconnect-modal
の id
を持つ 1 つの要素を定義します。 次の例では、ホスト ページに要素を配置します。
Pages/_Host.cshtml
:
<div id="components-reconnect-modal">
Connection lost.<br>Attempting to reconnect...
</div>
Note
components-reconnect-modal
の id
を持つ複数の要素がアプリによってレンダリングされる場合、最初にレンダリングされた要素のみが CSS クラスの変更を受け取り、要素を表示または非表示にします。
次の CSS スタイルをサイトのスタイルシートに追加します。
wwwroot/app.css
:
wwwroot/css/site.css
:
#components-reconnect-modal {
display: none;
}
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
display: block;
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
text-align: center;
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
margin: 50px 50px;
position: fixed;
top: 0;
z-index: 10001;
}
次の表で、Blazor フレームワークによってcomponents-reconnect-modal
要素に適用される CSS クラスについて説明します。
CSS クラス | 示す内容... |
---|---|
components-reconnect-show |
接続が失われました。 クライアントによって再接続が試行されています。 モーダルを表示します。 |
components-reconnect-hide |
サーバーへのアクティブな接続が再確立されます。 モーダルを非表示にします。 |
components-reconnect-failed |
再接続に失敗しました。ネットワーク障害が原因である可能性があります。 再接続を試みるには、JavaScript で window.Blazor.reconnect() を呼び出します。 |
components-reconnect-rejected |
再接続が拒否されました。 サーバーに到達したが接続が拒否されたため、サーバー上のユーザーの状態が失われました。 アプリを再度読み込むには、JavaScript で location.reload() を呼び出します。 この接続状態は、次の場合に発生する可能性があります。
|
モーダル要素に対して、サイトの CSS で transition-delay
プロパティを設定して、再接続 UI が表示されるまでの遅延時間をカスタマイズします。 次の例では、移行遅延時間を 500 ms (既定値) から 1,000 ms (1 秒) に設定しています。
wwwroot/app.css
:
wwwroot/css/site.css
:
#components-reconnect-modal {
transition: visibility 0s linear 1000ms;
}
現在の再接続の試行を表示するには、components-reconnect-current-attempt
の id
を使って要素を定義します。 再接続の再試行の最大数を表示するには、components-reconnect-max-retries
の id
を使って要素を定義します。 次の例では、前の例に従って、これらの要素を再接続試行モーダル要素内に配置します。
<div id="components-reconnect-modal">
There was a problem with the connection!
(Current reconnect attempt:
<span id="components-reconnect-current-attempt"></span> /
<span id="components-reconnect-max-retries"></span>)
</div>
カスタム再接続モーダルが表示されると、再接続試行カウンターを使用して次のコンテンツがレンダリングされます。
接続に関する問題がありました! (現在の再接続の試行回数: 1 / 8)
サーバー側のレンダリング
既定では、サーバーへのクライアント接続が確立される前に、コンポーネントがサーバー上でプリレンダリングされます。 詳細については、「ASP.NET Core コンポーネントRazorをプリレンダリングする」を参照してください。
既定では、サーバーへのクライアント接続が確立される前に、コンポーネントがサーバー上でプリレンダリングされます。 詳細については、「ASP.NET Core のコンポーネント タグ ヘルパー」を参照してください。
サーバー側の回線アクティビティを監視する
CircuitHandler 上で CreateInboundActivityHandler メソッドを使って、受信回線アクティビティを監視します。 受信回線アクティビティとは、UI イベントや JavaScript から .NET への相互運用呼び出しなど、ブラウザーからサーバーに送信されるすべてのアクティビティです。
たとえば、回線アクティビティ ハンドラーを使って、クライアントがアイドル状態かどうかとその回線 ID (Circuit.Id) を検出できます:
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;
public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
private Circuit? currentCircuit;
private readonly ILogger logger;
private readonly Timer timer;
public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger,
IOptions<IdleCircuitOptions> options)
{
timer = new Timer
{
Interval = options.Value.IdleTimeout.TotalMilliseconds,
AutoReset = false
};
timer.Elapsed += CircuitIdle;
this.logger = logger;
}
private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
{
logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
}
public override Task OnCircuitOpenedAsync(Circuit circuit,
CancellationToken cancellationToken)
{
currentCircuit = circuit;
return Task.CompletedTask;
}
public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
Func<CircuitInboundActivityContext, Task> next)
{
return context =>
{
timer.Stop();
timer.Start();
return next(context);
};
}
public void Dispose() => timer.Dispose();
}
public class IdleCircuitOptions
{
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}
public static class IdleCircuitHandlerServiceCollectionExtensions
{
public static IServiceCollection AddIdleCircuitHandler(
this IServiceCollection services,
Action<IdleCircuitOptions> configureOptions)
{
services.Configure(configureOptions);
services.AddIdleCircuitHandler();
return services;
}
public static IServiceCollection AddIdleCircuitHandler(
this IServiceCollection services)
{
services.AddScoped<CircuitHandler, IdleCircuitHandler>();
return services;
}
}
Program
ファイルにサービスを登録します。 次の例では、上記の IdleCircuitHandler
実装をテストするために、5 分から 5 秒の既定のアイドル タイムアウトを構成します:
builder.Services.AddIdleCircuitHandler(options =>
options.IdleTimeout = TimeSpan.FromSeconds(5));
また、回線アクティビティ ハンドラーには、他の Blazor ではない依存関係の挿入 (DI) スコープからスコープ付き Blazor サービスにアクセスするためのアプローチも用意されています。 詳細と例については、次をご覧ください。
Blazor スタートアップ。
Blazor Web App の App.razor
ファイル内にある Blazor の SignalR 回線の手動での起動を構成します。
Pages/_Host.cshtml
ファイル (Blazor Server) 内にある Blazor の SignalR 回線の手動での起動を構成します。
Pages/_Layout.cshtml
ファイル (Blazor Server) 内にある Blazor の SignalR 回線の手動での起動を構成します。
Pages/_Host.cshtml
ファイル (Blazor Server) 内にある Blazor の SignalR 回線の手動での起動を構成します。
blazor.*.js
スクリプトの<script>
タグにautostart="false"
属性を追加します。Blazor.start()
を呼び出すスクリプトを、Blazor スクリプトが読み込まれた後の終了</body>
タグ内に配置します。
autostart
が無効になっている場合、回線に依存しないアプリのすべての側面が正常に動作します。 たとえば、クライアント側のルーティングは動作します。 ただし、回線に依存する側面はすべて、Blazor.start()
が呼び出されるまで動作しません。 回線が確立されていなければ、アプリの動作は予測不可能です。 たとえば、回線が切断されている間、コンポーネント メソッドは実行できません。
ドキュメントの準備が完了したときに Blazor を初期化する方法や JS Promise
に連結する方法を含む詳細については、ASP.NET Core Blazor の起動に関する記事を参照してください。
クライアントで SignalR タイムアウトと Keep-Alive を構成する
クライアントに対して次の値を構成します。
-
withServerTimeout
: サーバーのタイムアウトをミリ秒単位で構成します。 サーバーからメッセージを受信せずにこのタイムアウトが経過すると、接続はエラーで終了します。 タイムアウトの既定値は 30 秒です。 サーバー タイムアウトは、Keep-Alive 間隔 (withKeepAliveInterval
) に割り当てられた値の少なくとも 2 倍にする必要があります。 -
withKeepAliveInterval
: キープ アライブ間隔 (サーバーに ping を実行する既定の間隔) をミリ秒単位で構成します。 この設定により、サーバーでハードの切断を検出できます。たとえば、クライアントがコンピューターをネットワークから取り外したときなどです。 ping は、最大でサーバーの ping と同じ頻度で発生します。 サーバーが 5 秒ごとに ping を実行する場合、5000
(5 秒) 未満の値を割り当てると 5 秒ごとに ping が実行されます。 既定値は 15 秒です。 Keep-Alive 間隔は、サーバー タイムアウト (withServerTimeout
) に割り当てられた値の半分以下にする必要があります。
App.razor
ファイル (Blazor Web App) の次の例は、既定値の代入を示しています。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
}
}
});
</script>
Pages/_Host.cshtml
ファイル (Blazor Server、.NET 6 の ASP.NET Core を除くすべてのバージョン) または Pages/_Layout.cshtml
ファイル (Blazor Server、.NET 6 の ASP.NET Core) の例を次に示します。
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(30000).withKeepAliveInterval(15000);
}
});
</script>
前の例の {BLAZOR SCRIPT}
プレースホルダーは、Blazor スクリプトのパスとファイル名です。 スクリプトの場所と使用するパスについては、「ASP.NET Core Blazor プロジェクトの構造」を参照してください。
コンポーネントでハブ接続を作成する場合は、HubConnectionBuilder に ServerTimeout (既定値: 30 秒) と KeepAliveInterval (既定値: 15 秒) を設定します。 ビルドされた HubConnection に HandshakeTimeout (既定値: 15 秒) を設定します。 次の例は、既定値の代入を示しています。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(30))
.WithKeepAliveInterval(TimeSpan.FromSeconds(15))
.Build();
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
クライアントに対して次の値を構成します。
-
serverTimeoutInMilliseconds
: サーバーのミリ秒単位のタイムアウト。 サーバーからメッセージを受信せずにこのタイムアウトが経過すると、接続はエラーで終了します。 タイムアウトの既定値は 30 秒です。 サーバー タイムアウトは、Keep-Alive 間隔 (keepAliveIntervalInMilliseconds
) に割り当てられた値の少なくとも 2 倍にする必要があります。 -
keepAliveIntervalInMilliseconds
: サーバーに ping を実行する既定の間隔。 この設定により、サーバーでハードの切断を検出できます。たとえば、クライアントがコンピューターをネットワークから取り外したときなどです。 ping は、最大でサーバーの ping と同じ頻度で発生します。 サーバーが 5 秒ごとに ping を実行する場合、5000
(5 秒) 未満の値を割り当てると 5 秒ごとに ping が実行されます。 既定値は 15 秒です。 Keep-Alive 間隔は、サーバー タイムアウト (serverTimeoutInMilliseconds
) に割り当てられた値の半分以下にする必要があります。
Pages/_Host.cshtml
ファイル (Blazor Server、.NET 6 の ASP.NET Core を除くすべてのバージョン) または Pages/_Layout.cshtml
ファイル (Blazor Server、.NET 6 の ASP.NET Core) の例を次に示します。
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 30000;
c.keepAliveIntervalInMilliseconds = 15000;
builder.build = () => {
return c;
};
}
});
</script>
前の例の {BLAZOR SCRIPT}
プレースホルダーは、Blazor スクリプトのパスとファイル名です。 スクリプトの場所と使用するパスについては、「ASP.NET Core Blazor プロジェクトの構造」を参照してください。
コンポーネントでハブ接続を作成する場合は、ビルドされた HubConnection に ServerTimeout (既定値: 30 秒)、HandshakeTimeout (既定値: 15 秒)、KeepAliveInterval (既定値: 15 秒) を設定します。 次の例は、既定値の代入を示しています。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
サーバー タイムアウト (ServerTimeout) またはキープ アライブ間隔 (KeepAliveInterval) の値を変更する場合:
- サーバー タイムアウトは、Keep-Alive 間隔に割り当てられた値の少なくとも 2 倍にする必要があります。
- Keep-Alive 間隔は、サーバー タイムアウトに割り当てられた値の半分以下にする必要があります。
詳細については、以下の記事の「グローバル展開と接続エラー」のセクションを参照してください。
サーバー側の再接続ハンドラーを変更する
再接続ハンドラーの回線接続イベントは、次のようなカスタム動作を行うように変更できます。
- 接続が切断された場合にユーザーに通知する。
- 回線が接続されているときに (クライアントから) ログ記録を実行する。
接続イベントを変更するには、次の接続の変更に対してコールバックを登録します。
- 切断された接続では、
onConnectionDown
が使用されます。 - 確立または再確立された接続では、
onConnectionUp
が使用されます。
onConnectionDown
と onConnectionUp
の両方を指定する必要があります。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
reconnectionHandler: {
onConnectionDown: (options, error) => console.error(error),
onConnectionUp: () => console.log("Up, up, and away!")
}
});
</script>
前の例の {BLAZOR SCRIPT}
プレースホルダーは、Blazor スクリプトのパスとファイル名です。 スクリプトの場所と使用するパスについては、「ASP.NET Core Blazor プロジェクトの構造」を参照してください。
サーバー側の再接続が失敗したときにページを自動的に更新する
既定の再接続動作では、再接続が失敗した後にページを更新するための手動アクションをユーザーが行う必要があります。 ただし、カスタム再接続ハンドラーを使用すると、ページを自動的に更新できます。
App.razor
:
Pages/_Host.cshtml
:
<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>
前の例の {BLAZOR SCRIPT}
プレースホルダーは、Blazor スクリプトのパスとファイル名です。 スクリプトの場所と使用するパスについては、「ASP.NET Core Blazor プロジェクトの構造」を参照してください。
次の wwwroot/boot.js
ファイルを作成します。
Blazor Web App:
(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');
const startReconnectionProcess = () => {
reconnectModal.style.display = 'block';
let isCanceled = false;
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
if (isCanceled) {
return;
}
try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected; reload the page.
location.reload();
return;
}
// Successfully reconnected to the server.
return;
} catch {
// Didn't reach the server; try again.
}
}
// Retried too many times; reload the page.
location.reload();
})();
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
let currentReconnectionProcess = null;
Blazor.start({
circuit: {
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
}
});
})();
Blazor Server:
(() => {
const maximumRetryCount = 3;
const retryIntervalMilliseconds = 5000;
const reconnectModal = document.getElementById('reconnect-modal');
const startReconnectionProcess = () => {
reconnectModal.style.display = 'block';
let isCanceled = false;
(async () => {
for (let i = 0; i < maximumRetryCount; i++) {
reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;
await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));
if (isCanceled) {
return;
}
try {
const result = await Blazor.reconnect();
if (!result) {
// The server was reached, but the connection was rejected; reload the page.
location.reload();
return;
}
// Successfully reconnected to the server.
return;
} catch {
// Didn't reach the server; try again.
}
}
// Retried too many times; reload the page.
location.reload();
})();
return {
cancel: () => {
isCanceled = true;
reconnectModal.style.display = 'none';
},
};
};
let currentReconnectionProcess = null;
Blazor.start({
reconnectionHandler: {
onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
onConnectionUp: () => {
currentReconnectionProcess?.cancel();
currentReconnectionProcess = null;
}
}
});
})();
Blazor の起動の詳細については、「ASP.NET Core Blazor の起動」をご覧ください。
サーバー側の再接続の再試行回数と間隔を調整する
再接続の再試行の回数と間隔を調整するには、再試行の回数 (maxRetries
) と、各再試行で許可されるミリ秒単位の期間 (retryIntervalMilliseconds
) を設定します。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
reconnectionOptions: {
maxRetries: 3,
retryIntervalMilliseconds: 2000
}
});
</script>
前の例の {BLAZOR SCRIPT}
プレースホルダーは、Blazor スクリプトのパスとファイル名です。 スクリプトの場所と使用するパスについては、「ASP.NET Core Blazor プロジェクトの構造」を参照してください。
ユーザーが、回線が切断されたアプリに戻ると、次の再接続間隔を待たずに、すぐに再接続が試行されます。 この動作は、ユーザーが接続をできるだけ早く再開できるようにすることを目的としています。
既定の再接続のタイミングでは、計算されたバックオフ戦略が使用されます。 試行の間に計算された遅延が採用されるまで、最初の数回は短い間隔で再接続が試行されます。 再試行間隔を計算するための既定のロジックの実装の詳細は、予告なしに変更される可能性がありますが、Blazor フレームワークで使用される既定のロジックは、computeDefaultRetryInterval
関数 (参照ソース) で確認できます。
Note
通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。
再試行間隔を計算する関数を指定して、再試行間隔の動作をカスタマイズします。 次の指数バックオフの例では、以前の再接続試行回数に 1,000 ミリ秒を掛けて再試行間隔を計算します。 以前の再接続試行回数 (previousAttempts
) が再試行回数の上限 (maxRetries
) より大きい場合、再試行間隔 (retryIntervalMilliseconds
) に null
が割り当てられ、それ以降の再接続試行は停止されます。
Blazor.start({
circuit: {
reconnectionOptions: {
retryIntervalMilliseconds: (previousAttempts, maxRetries) =>
previousAttempts >= maxRetries ? null : previousAttempts * 1000
},
},
});
別の方法として、再試行間隔の正確なシーケンスを指定します。 最後に指定された再試行間隔の後、retryIntervalMilliseconds
関数は undefined
を返すため再試行は停止します。
Blazor.start({
circuit: {
reconnectionOptions: {
retryIntervalMilliseconds:
Array.prototype.at.bind([0, 1000, 2000, 5000, 10000, 15000, 30000]),
},
},
});
Blazor の起動の詳細については、「ASP.NET Core Blazor の起動」をご覧ください。
再接続 UI が表示されるタイミングを制御する
再接続 UI が表示されるタイミングを制御すると、次のような場合に役立ちます。
- デプロイされたアプリは、内部ネットワークまたはインターネットの待機時間による ping のタイムアウトが原因で再接続 UI を頻繁に表示します。また、遅延期間を増やしたい場合があります。
- アプリは、接続が切断されたことをユーザーに素早く報告する必要があるため、遅延を短くしたい場合があります。
再接続 UI の表示タイミングは、クライアントのキープ アライブ間隔とタイムアウトを調整することで影響されます。 再接続 UI は、クライアント (withServerTimeout
、 クライアント構成セクション) でサーバー タイムアウトに達すると表示されます。 ただし、withServerTimeout
の値を変更するには、次のガイダンスで説明されている他のキープ アライブ設定、タイムアウト設定、ハンドシェイク設定を変更する必要があります。
一般的なレコメンデーションとして次のようなガイダンスがあります。
- キープ アライブ間隔は、クライアントとサーバーの構成間で一致する必要があります。
- タイムアウトは、キープ アライブ間隔に割り当てられた値の少なくとも 2 倍にする必要があります。
サーバー構成
次のように設定します。
- ClientTimeoutInterval (既定値: 30 秒): 時間枠クライアントは、サーバーが接続を閉じる前にメッセージを送信する必要があります。
- HandshakeTimeout (既定値: 15 秒): クライアントによる着信ハンドシェイク要求をタイムアウトするためにサーバーで使用される間隔。
- KeepAliveInterval (既定値: 15 秒): 接続済みクライアントにキープ アライブ ping を送信するためにサーバーで使用される間隔。 クライアントにもキープ アライブ間隔の設定があり、サーバーの値と一致する必要があることに注意します。
ClientTimeoutInterval と HandshakeTimeout は増やせますが、KeepAliveInterval はそのままにします。 重要な考慮事項として、値を変更する場合は、タイムアウト値がキープ アライブ間隔の値の少なくとも 2 倍になるように、またサーバーのキープ アライブ間隔がクライアント設定と一致するようにする必要があります。 詳細については、「クライアントでタイムアウトとSignalRキープ アライブを構成する」セクションを参照してください。
次に例を示します。
- ClientTimeoutInterval を、60 秒に増やします (既定値: 30 秒)。
- HandshakeTimeout を、30 秒に増やします (既定値: 15 秒)。
- KeepAliveInterval は開発者コードでは設定されていないので、既定値の 15 秒が使用されます。 キープ アライブ間隔の値を小さくすると、通信 ping の頻度が増加し、アプリ、サーバー、ネットワークの負荷が増加します。 キープ アライブ間隔を短縮する場合は、パフォーマンスが低下しないように注意する必要があります。
サーバー プロジェクトの Program
ファイルの Blazor Web App (.NET 8 以降):
builder.Services.AddRazorComponents().AddInteractiveServerComponents()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
Program
ファイルの Blazor Server:
builder.Services.AddServerSideBlazor()
.AddHubOptions(options =>
{
options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});
詳細については、「サーバー側の回線ハンドラーのオプション」セクションを参照してください。
クライアントの構成
次のように設定します。
-
withServerTimeout
(既定値: 30 秒): 回線のハブ接続に対して、ミリ秒単位で指定されたサーバー タイムアウトを構成します。 -
withKeepAliveInterval
(既定値: 15 秒): 接続がキープ アライブ メッセージを送信するミリ秒単位の間隔。
サーバーのタイムアウトを増やすことができます。キープ アライブ間隔はそのままにすることができます。 重要な考慮事項として、値を変更する場合は、サーバー タイムアウト値がキープ アライブ間隔の値の少なくとも 2 倍になるように、またサーバーのキープ アライブ間隔がクライアント設定と一致するようにする必要があります。 詳細については、「クライアントでタイムアウトとSignalRキープ アライブを構成する」セクションを参照してください。
次の スタートアップ構成例 (Blazor スクリプトの ロケーション) では、カスタム値である 60 秒がサーバー タイムアウトに使用されます。 キープ アライブ間隔 (withKeepAliveInterval
) は設定されておらず、既定値の 15 秒を使用します。
Blazor Web App:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
circuit: {
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
}
});
</script>
Blazor Server:
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
builder.withServerTimeout(60000);
}
});
</script>
コンポーネントでハブ接続を作成する場合は、HubConnectionBuilder でサーバー タイムアウト (WithServerTimeout、既定値: 30 秒) を設定します。 ビルドされた HubConnection に HandshakeTimeout (既定値: 15 秒) を設定します。 タイムアウトがキープ アライブ間隔 (WithKeepAliveInterval/KeepAliveInterval) の 2 倍以上であり、キープ アライブ値がサーバーとクライアントの間で一致していることを確認します。
次の例は、Blazor チュートリアルがある SignalR の Index
コンポーネントに基づいています。 サーバー タイムアウトを 60 秒に増やし、ハンドシェイク タイムアウトを 30 秒に増やしています。 キープ アライブ間隔は設定されておらず、既定値の 15 秒を使用します。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.WithServerTimeout(TimeSpan.FromSeconds(60))
.Build();
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
次のように設定します。
-
serverTimeoutInMilliseconds
(既定値: 30 秒): 回線のハブ接続に対して、ミリ秒単位で指定されたサーバー タイムアウトを構成します。 -
keepAliveIntervalInMilliseconds
(既定値: 15 秒): 接続がキープ アライブ メッセージを送信するミリ秒単位の間隔。
サーバーのタイムアウトを増やすことができます。キープ アライブ間隔はそのままにすることができます。 重要な考慮事項として、値を変更する場合は、サーバー タイムアウト値がキープ アライブ間隔の値の少なくとも 2 倍になるように、またサーバーのキープ アライブ間隔がクライアント設定と一致するようにする必要があります。 詳細については、「クライアントでタイムアウトとSignalRキープ アライブを構成する」セクションを参照してください。
次の スタートアップ構成例 (Blazor スクリプトの ロケーション) では、カスタム値である 60 秒がサーバー タイムアウトに使用されます。 キープ アライブ間隔 (keepAliveIntervalInMilliseconds
) は設定されておらず、既定値の 15 秒を使用します。
Pages/_Host.cshtml
の場合:
<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
Blazor.start({
configureSignalR: function (builder) {
let c = builder.build();
c.serverTimeoutInMilliseconds = 60000;
builder.build = () => {
return c;
};
}
});
</script>
コンポーネントでハブ接続を作成する場合は、ビルドされた HubConnection に ServerTimeout (既定値: 30 秒) と HandshakeTimeout (既定値: 15 秒) を設定します。 タイムアウトがキープ アライブ間隔の 2 倍以上であることを確認します。 キープ アライブ間隔がサーバーとクライアント間で一致することを確認します。
次の例は、Blazor チュートリアルがある SignalR の Index
コンポーネントに基づいています。 ServerTimeout を 60 秒に増やし、HandshakeTimeout を 30 秒に増やします。 キープ アライブ間隔 (KeepAliveInterval) は設定されておらず、既定値の 15 秒を使用します。
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Navigation.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);
hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...
await hubConnection.StartAsync();
}
クライアントから Blazor の SignalR 回線を切断する
unload
ページ イベントがトリガーされると、Blazor の SignalR 回線は切断されます。 クライアント上の他のシナリオで回線を切断するには、適切なイベント ハンドラーで Blazor.disconnect
を呼び出します。 次の例では、ページが非表示になると、回線が切断されます (pagehide
イベント)。
window.addEventListener('pagehide', () => {
Blazor.disconnect();
});
Blazor の起動の詳細については、「ASP.NET Core Blazor の起動」をご覧ください。
サーバー側の回線ハンドラー
ユーザー回線の状態変化時にコードを実行できる "回線ハンドラー" を定義できます。 回線ハンドラーは、CircuitHandler から派生させ、そのクラスをアプリのサービス コンテナーに登録することで実装します。 次の回線ハンドラーの例では、開いている SignalR 接続を追跡します。
TrackingCircuitHandler.cs
:
using Microsoft.AspNetCore.Components.Server.Circuits;
public class TrackingCircuitHandler : CircuitHandler
{
private HashSet<Circuit> circuits = new();
public override Task OnConnectionUpAsync(Circuit circuit,
CancellationToken cancellationToken)
{
circuits.Add(circuit);
return Task.CompletedTask;
}
public override Task OnConnectionDownAsync(Circuit circuit,
CancellationToken cancellationToken)
{
circuits.Remove(circuit);
return Task.CompletedTask;
}
public int ConnectedCircuits => circuits.Count;
}
回線ハンドラーは DI を使用して登録されます。 スコープを持つインスタンスは、回線のインスタンスごとに作成されます。 前の例の TrackingCircuitHandler
を使用すると、すべての回線の状態を追跡する必要があるため、シングルトン サービスが作成されます。
Program
ファイルで次の操作を行います。
builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
Startup.cs
の Startup.ConfigureServices
で:
services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();
カスタム回線ハンドラーのメソッドでハンドルされない例外がスローされる場合は、その例外は回線にとって致命的です。 ハンドラーのコードまたはメソッドで例外が許容されるようにするには、エラー処理とログを含む 1 つ以上の try-catch
ステートメントでコードをラップします。
ユーザーが切断し、フレームワークで回線の状態がクリーンアップされていることが原因で回線が終了すると、フレームワークによって回線の DI スコープが破棄されます。 スコープが破棄されると、System.IDisposable を実装するサーキットスコープの DI サービスはすべて破棄されます。 破棄中にいずれかの DI サービスでハンドルされない例外がスローされると、フレームワークによって例外がログに記録されます。 詳細については、「ASP.NET Core Blazor の依存関係の挿入」を参照してください。
カスタム サービスのユーザーをキャプチャするためのサーバー側回線ハンドラー
AuthenticationStateProvider からユーザーをキャプチャして、サービスでそのユーザーを設定するには、CircuitHandler を使います。 詳細とコード例については、「ASP.NET Core のサーバー側と Blazor Web App の追加セキュリティ シナリオ」を参照してください。
対話型サーバー コンポーネントが残っていない場合に回線を閉じる
対話型サーバー コンポーネントは、回線と呼ばれるブラウザーとのリアルタイム接続を使用して Web UI イベントを処理します。 ルート対話型サーバー コンポーネントがレンダリングされる際、回線およびその関連付けられた状態が作成されます。 ページ上に対話型サーバー コンポーネントが残っていない場合、回線は閉じられ、サーバー リソースは解放されます。
別の URL で SignalR 回線を開始する
autostart="false"
Blazor タグ (Blazor) に を追加して、アプリが自動的に起動しないようにします。 Blazor.start
を使用して回線 URL を手動で確立します。 次の例では、パス /signalr
を使用しています。
Blazor Web Apps:
- <script src="_framework/blazor.web.js"></script>
+ <script src="_framework/blazor.web.js" autostart="false"></script>
+ <script>
+ Blazor.start({
+ circuit: {
+ configureSignalR: builder => builder.withUrl("/signalr")
+ },
+ });
+ </script>
Blazor Server:
- <script src="_framework/blazor.server.js"></script>
+ <script src="_framework/blazor.server.js" autostart="false"></script>
+ <script>
+ Blazor.start({
+ configureSignalR: builder => builder.withUrl("/signalr")
+ });
+ </script>
次の MapBlazorHub 呼び出しを、サーバー アプリの Program
ファイル内のミドルウェア処理パイプラインへのハブ パスで追加します。
Blazor Web Apps:
app.MapBlazorHub("/signalr");
Blazor Server:
既存の MapBlazorHub 呼び出しをファイル内にそのまま残し、新たにパスを指定して MapBlazorHub を呼び出すように追加します。
app.MapBlazorHub();
+ app.MapBlazorHub("/signalr");
Windows 認証の偽装
認証されたハブ接続 (HubConnection) は、HTTP 要求に既定の資格情報を使用することを示す UseDefaultCredentials で作成されます。 詳細については、「ASP.NET Core SignalRでの認証と承認の」を参照してください。
アプリが Windows 認証の下でサインインユーザーとして IIS Express で実行されている場合(ユーザーの個人アカウントまたは職場アカウントである可能性があります)、既定の資格情報はサインインしているユーザーの資格情報です。
アプリが IIS に発行されると、アプリは アプリケーション プール Identityで実行されます。 HubConnection は、ページにアクセスするユーザーではなく、アプリをホストする IIS "ユーザー" アカウントとして接続します。
で HubConnection を実装して、閲覧中のユーザーのアイデンティティを使用します。
次に例を示します。
- 認証状態プロバイダーのユーザーは、WindowsIdentityにキャストされます。
- アイデンティティのアクセス トークンは、WindowsIdentity.RunImpersonatedAsyncをビルドして開始するコードにより HubConnection に渡されます。
protected override async Task OnInitializedAsync()
{
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState?.User.Identity is not null)
{
var user = authState.User.Identity as WindowsIdentity;
if (user is not null)
{
await WindowsIdentity.RunImpersonatedAsync(user.AccessToken,
async () =>
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavManager.ToAbsoluteUri("/hub"), config =>
{
config.UseDefaultCredentials = true;
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string>("name", userName =>
{
name = userName;
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
});
}
}
}
上記のコードでは、NavManager
は NavigationManagerであり、AuthenticationStateProvider
は AuthenticationStateProvider サービス インスタンスです (AuthenticationStateProvider
ドキュメント)。
その他のサーバー側リソース
- サーバー側のホストとデプロイのガイダンス: SignalR の構成
- ASP.NET Core の概要SignalR
- ASP.NET Core SignalR の構成
- サーバー側のセキュリティに関するドキュメント
- ASP.NET Core Blazor アプリにおける IHttpContextAccessor/HttpContext
- サーバー側の再接続イベントとコンポーネント ライフサイクル イベント
- Azure SignalR Service とは
- Azure SignalR Service のパフォーマンス ガイド
- Azure App Service に ASP.NET Core SignalR アプリを発行する
- Blazor サンプル GitHub リポジトリ (
dotnet/blazor-samples
) (ダウンロード方法)
ASP.NET Core