ASP.NET Core BlazorSignalR ガイダンス

注意

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

この記事では、Blazor アプリで SignalR 接続を構成および管理する方法について説明します。

ASP.NET Core SignalR の構成に関する一般的なガイダンスについては、ドキュメントの ASP.NET Core SignalR の概要に関するトピック (特に、ASP.NET Core SignalR の構成) をご覧ください。

サーバー側のアプリでは、ブラウザーとの通信に ASP.NET Core SignalR が使用されます。 SignalR のホストとスケーリングの条件は、サーバー側のアプリに適用されます。

Blazor は、待ち時間、信頼性、およびセキュリティが低いために WebSocket を SignalR トランスポートとして使用する場合に最適です。 WebSocket が使用できない場合や、ロング ポーリングを使用するようにアプリが明示的に構成されている場合は、SignalR によってロング ポーリングが使用されます。

対話型サーバー コンポーネントの WebSocket 圧縮

既定では、対話型サーバー コンポーネントは以下を実行します。

  • WebSocket 接続の圧縮を有効にします。 ConfigureWebsocketOptions が WebSocket 圧縮を制御します。

  • 'self' に設定された frame-ancestorsコンテンツ セキュリティ ポリシー (CSP) ディレクティブを採用します。このディレクティブは、圧縮が有効になっているか、WebSocket コンテキストの構成が提供される場合にだけ、アプリをそのアプリの提供元の <iframe> に埋め込むことを許可します。 ContentSecurityFrameAncestorPolicyframe-ancestors CSP を制御します。

CSP を一元的な方法で構成する必要がある場合、frame-ancestors CSP は、ConfigureWebSocketOptions の値を null に設定することで手動で削除できます。 frame-ancestors CSP を一元的に管理する場合は、最初のドキュメントがレンダリングされるたびにポリシーを適用するように注意する必要があります。 アプリが攻撃に対して脆弱になる可能性があるため、ポリシーを完全に削除することはお勧めしません。

使用例:

ConfigureWebSocketOptionsnull に設定することで圧縮を無効にします。これにより、アプリの攻撃に対する脆弱性は減少しますが、パフォーマンスが低下する可能性があります。

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

圧縮が有効になっている場合は、'none' という値 (単一引用符が必要) を使用してより厳密な frame-ancestors CSP を構成します。これにより、WebSocket 圧縮は実行できますが、ブラウザーはアプリを <iframe> に埋め込むことができなくなります。

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

圧縮が有効になっている場合は、ContentSecurityFrameAncestorsPolicynull に設定することで frame-ancestors CSP を削除します。 このシナリオが推奨されるのは、一元化された方法で CSP を設定するアプリに対してだけです。

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

重要

ブラウザーは、最も厳密なポリシー ディレクティブ値を使用して、複数の CSP ヘッダーの CSP ディレクティブを適用します。 したがって、開発者は意図的または誤って 'self' より弱い frame-ancestors ポリシーを追加することができません。

ContentSecurityFrameAncestorsPolicy に渡される文字列値には、単一引用符が必要です。

サポートされていない値:noneself

サポートされている値:'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);
    }
}

ハブ接続が構築されている場合は、HttpMessageHandlerHttpMessageHandlerFactory オプションに割り当てます。

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 メッセージ サイズの制限

大規模なプリレンダリングされた状態サイズが SignalR 回線メッセージ サイズの制限を超える可能性があり、その結果、次のようになります。

  • この SignalR 回線は、クライアントで次のエラーで初期化に失敗します: Circuit host not initialized.
  • 回線が失敗すると、クライアントの再接続ダイアログが表示されます。 復旧はできません。

この問題を解決するには、次の "いずれかの" 方法を使用します。

  • プリレンダリングされた状態に入れるデータの量を減らします。
  • SignalR メッセージ サイズの制限を増やします。 警告: 上限を引き上げると、サービス拒否 (DoS) 攻撃のリスクが高まる可能性があります。

その他のクライアント側リソース

サーバー側の webfarm ホスティングにスティッキー セッションを使用する

最初のクライアント要求への応答として、Blazor アプリによってプリレンダリングされます。これにより、サーバー上で UI の状態が作成されます。 クライアントで SignalR 接続の作成が再試行される際は、クライアントを同じサーバーに再接続する必要があります。 複数のバックエンド サーバーが使用されている場合、アプリでは、SignalR 接続に "スティッキー セッション" を実装する必要があります。

メモ

Webfarm でスティッキー セッションを有効にしていないアプリからは次のエラーがスローされます。

blazor.server.js:1 Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed. (blazor.server.js:1 捕捉されない (promise 内の) エラー: 基となる接続が閉じられたため、呼び出しが取り消されました。)

サーバー側の Azure SignalR Service

Microsoft Azure でホストされているサーバー側の開発には、Azure SignalR Service を使用することをお勧めします。 このサービスはアプリの Blazor Hub と連携して、サーバー側のアプリを多数の SignalR 同時接続にスケールアップします。 さらに、SignalR Service のグローバル リーチとハイパフォーマンスのデータ センターは、地理的条件による待機時間の短縮に役立ちます。

Azure SignalR サービスでは、サービスの ServerStickyMode オプションまたは構成値を Required に設定することにより、スティッキー セッションが有効になります。 詳細については、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.csStartup.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.csStartup.ConfigureServices で:

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

警告

MaximumReceiveMessageSize の既定値は 32 KB です。 この値を大きくすると、サービス拒否 (DoS) 攻撃のリスクが高まるおそれがあります。

メモリ管理の詳細については、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};
});

アプリ 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 のログがSignalR または 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 相互運用データを生バイトとして利用できる場合に使用できます。 InputFile コンポーネントに似た手法を使ってサーバー側アプリで大規模なバイナリ ペイロードを送信する方法の例については、,Binary Submit サンプル アプリBlazorInputLargeTextArea コンポーネント サンプルを参照してください。

メモ

通常、.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 相互運用データを生バイトとして利用できる場合に使用できます。 Blazor Server で大規模なバイナリ ペイロードを送信する、InputFile コンポーネントに似た手法を使用する方法の例については、Binary Submit サンプル アプリに関する記事、およびBlazorInputLargeTextArea コンポーネント サンプルに関する記事を参照してください。

メモ

通常、.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.ConfigureServicesMaximumReceiveMessageSize を設定します。

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR の受信メッセージのサイズ制限を増やすと、サーバー リソースを増やす必要があり、サービス拒否 (DoS) 攻撃のリスクが高くなります。 また、大量のコンテンツを文字列またはバイト配列としてメモリに読み取ると、ガベージ コレクターがうまく機能しない割り当てが発生する可能性もあり、その結果、パフォーマンスがさらに低下します。

大量のデータを転送するコードを開発するときは、次のガイダンスを考慮してください。

  • SignalR の受信メッセージ サイズの制限を超えるデータを転送するには、ネイティブ ストリーミング JS 相互運用サポートを利用します。
  • 一般的なヒント:
    • JS および C# コードで大きなオブジェクトを割り当てないでください。
    • プロセスの完了時またはキャンセル時に、消費していたメモリを解放します。
    • セキュリティ上の理由から、次の追加要件を適用します。
      • 渡すことのできるファイルまたはデータの最大サイズを宣言します。
      • クライアントからサーバーへの最小アップロード レートを宣言します。
    • データがサーバーによって受信されたら、データは:
      • すべてのセグメントが収集されるまで、一時的にメモリ バッファーに格納できます。
      • 直ちに消費できます。 たとえば、データは、データベースに直ちに格納することも、セグメントを受信するたびにディスクに書き込むこともできます。
  • データをより小さな部分にスライスし、すべてのデータがサーバーによって受信されるまでデータ セグメントを順番に送信します。
  • JS および C# コードで大きなオブジェクトを割り当てないでください。
  • データを送受信するときに、メイン UI スレッドを長時間ブロックしないでください。
  • プロセスの完了時またはキャンセル時に、消費していたメモリを解放します。
  • セキュリティ上の理由から、次の追加要件を適用します。
    • 渡すことのできるファイルまたはデータの最大サイズを宣言します。
    • クライアントからサーバーへの最小アップロード レートを宣言します。
  • データがサーバーによって受信されたら、データは:
    • すべてのセグメントが収集されるまで、一時的にメモリ バッファーに格納できます。
    • 直ちに消費できます。 たとえば、データは、データベースに直ちに格納することも、セグメントを受信するたびにディスクに書き込むこともできます。

Blazor サーバー側 Hub のエンドポイント ルート構成

Program ファイルで MapBlazorHub を呼び出して、BlazorHub をアプリの既定のパスにマップします。 Blazor スクリプト (blazor.*.js) は、MapBlazorHub によって作成されたエンドポイントを自動的に指します。

UI にサーバー側の接続状態を反映する

接続が失われたことがクライアントで検出されると、クライアントによって再接続が試行される間、ユーザーに対して既定の UI が表示されます。 再接続に失敗した場合、ユーザーには再試行のオプションが表示されます。

UI をカスタマイズするには、components-reconnect-modalid を持つ単一の要素を定義します。 次の例では、App コンポーネントに要素を配置します。

App.razor:

UI をカスタマイズするには、components-reconnect-modalid を持つ単一の要素を定義します。 次の例では、ホスト ページに要素を配置します。

Pages/_Host.cshtml:

UI をカスタマイズするには、components-reconnect-modalid を持つ単一の要素を定義します。 次の例では、レイアウト ページに要素を配置します。

Pages/_Layout.cshtml:

UI をカスタマイズするには、components-reconnect-modalid を持つ単一の要素を定義します。 次の例では、ホスト ページに要素を配置します。

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

注意

components-reconnect-modalid を持つ複数の要素がアプリによってレンダリングされる場合、最初にレンダリングされた要素のみが 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;
}

次の表で、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 プロパティを設定して、再接続表示が表示されるまでの遅延時間をカスタマイズします。 次の例では、移行遅延時間を 500 ms (既定値) から 1,000 ms (1 秒) に設定しています。

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

現在の再接続の試行を表示するには、components-reconnect-current-attemptid を使って要素を定義します。 再接続の再試行の最大数を表示するには、components-reconnect-max-retriesid を使って要素を定義します。 次の例では、前の例に従って、これらの要素を再接続試行モーダル要素内に配置します。

<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>

カスタムの再接続モーダルが表示される際に、前のコードに基づいて次のようなコンテンツがレンダリングされます。

There was a problem with the connection! (Current reconnect attempt: 3 / 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.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: Keep-Alive 間隔 (サーバーに ping を実行する既定の間隔) をミリ秒単位で構成します。 この設定により、サーバーでハードの切断を検出できます。たとえば、クライアントがコンピューターをネットワークから取り外したときなどです。 ping は、最大でサーバーの ping と同じ頻度で発生します。 サーバーが 5 秒ごとに ping を実行する場合、5000 (5 秒) 未満の値を割り当てると 5 秒ごとに ping が実行されます。 既定値は 15 秒です。 Keep-Alive 間隔は、サーバー タイムアウト (withServerTimeout) に割り当てられた値の半分以下にする必要があります。

App.razor ファイル (Blazor Web アプリ) の次の例は、既定値の代入を示しています。

Blazor Web アプリ:

<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 プロジェクトの構造」をご覧ください。

コンポーネントでハブ接続を作成する場合は、HubConnectionBuilderServerTimeout (既定値: 30 秒) と KeepAliveInterval (既定値: 15 秒) を設定します。 ビルドされた HubConnectionHandshakeTimeout (既定値: 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 プロジェクトの構造」をご覧ください。

コンポーネントでハブ接続を作成する場合は、ビルドされた HubConnectionServerTimeout (既定値: 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) または Keep-Alive 間隔 (KeepAliveInterval) の値を変更する場合:

  • サーバー タイムアウトは、Keep-Alive 間隔に割り当てられた値の少なくとも 2 倍にする必要があります。
  • Keep-Alive 間隔は、サーバー タイムアウトに割り当てられた値の半分以下にする必要があります。

詳細については、以下の記事の「グローバル展開と接続エラー」のセクションを参照してください。

サーバー側の再接続ハンドラーを変更する

再接続ハンドラーの回線接続イベントは、次のようなカスタム動作を行うように変更できます。

  • 接続が切断された場合にユーザーに通知する。
  • 回線が接続されているときに (クライアントから) ログ記録を実行する。

接続イベントを変更するには、次の接続の変更に対してコールバックを登録します。

  • 切断された接続では、onConnectionDown が使用されます。
  • 確立または再確立された接続では、onConnectionUp が使用されます。

onConnectionDownonConnectionUp の両方を指定する必要があります。

Blazor Web アプリ:

<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 アプリ:

(() => {
  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 アプリ:

<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 の起動の詳細については、「ASP.NET Core Blazor の起動」をご覧ください。

再接続 UI が表示されるタイミングを制御する

再接続 UI が表示されるタイミングを制御すると、次のような場合に役立ちます。

  • デプロイされたアプリでは、内部ネットワークまたはインターネットの待機時間によって ping タイムアウトが発生したため、再接続 UI が頻繁に表示されるので、延期期間を増やしたい場合。
  • あるアプリで、接続が早く切断されたことをユーザーに報告する必要があるため、延期期間を短縮したい場合。

再接続 UI の出現のタイミングは、クライアントのキープアライブ間隔とタイムアウトの調整による影響を受けます。 ただし、設定を変更するには、他のキープアライブ、タイムアウト、ハンドシェイクの設定の変更が必要な場合があります。

一般的な推奨事項として次のようなガイダンスがあります。

  • キープアライブ間隔は、クライアントとサーバーの構成が一致する必要があります。
  • タイムアウトは、キープアライブ間隔に割り当てられた値の 2 倍以上である必要があります。

サーバー構成

次のように設定します。

  • ClientTimeoutInterval (既定値: 30 秒): 時間枠クライアントは、サーバーが接続を閉じる前にメッセージを送信する必要があります。
  • HandshakeTimeout (既定値: 15 秒): クライアントによる着信ハンドシェイク要求のタイムアウトにサーバーで使用される間隔。
  • KeepAliveInterval (既定値: 15 秒): 接続済みクライアントにキープ アライブ ping を送信するためにサーバーで使用される間隔。 クライアントにもキープアライブ間隔の設定があり、サーバーの値と一致する必要があることに注意してください。

ClientTimeoutIntervalHandshakeTimeout を増やすことができ、KeepAliveInterval はそのままでかまいません。 重要な考慮事項として、値を変更する場合は、タイムアウトがキープアライブ間隔の値の 2 倍以上になるようにし、またキープアライブ間隔はクライアントとサーバー間で一致する必要があります。 詳細については、「クライアントで SignalR タイムアウトと Keep-Alive を構成する」セクションを参照してください。

次に例を示します。

  • ClientTimeoutInterval を、60 秒に増やします (既定値: 30 秒)。
  • HandshakeTimeout を、30 秒 (既定値: 15 秒) に増やします。
  • KeepAliveInterval は開発者コードでは設定されていないので、既定値の 15 秒が使用されます。 キープアライブ間隔の値を小さくすると、通信 ping の頻度が増加し、アプリ、サーバー、ネットワークの負荷が増加します。 キープアライブ間隔を短縮する場合は、パフォーマンスが低下しないように注意する必要があります。

サーバー プロジェクトの Program ファイル内の Blazor Web アプリ (.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 タイムアウトと Keep-Alive を構成する」セクションを参照してください。

次のスタートアップ構成の例 (Blazorスクリプトの場所) では、サーバーのタイムアウトに 60 秒のカスタム値が使用されています。 キープアライブ間隔 (withKeepAliveInterval) は設定されておらず、既定値の 15 秒を使用します。

Blazor Web アプリ:

<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 秒) を設定します。 ビルドされた HubConnectionHandshakeTimeout (既定値: 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 タイムアウトと Keep-Alive を構成する」セクションを参照してください。

次のスタートアップ構成の例 (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>

コンポーネントでハブ接続を作成する場合は、ビルドされた HubConnectionServerTimeout (既定値: 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 回線を切断する

既定では、unload ページ イベントがトリガーされると、Blazor 回線が切断されます。 クライアント上の他のシナリオで回線を切断するには、適切なイベント ハンドラーで 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.csStartup.ConfigureServices で:

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

カスタム回線ハンドラーのメソッドでハンドルされない例外がスローされる場合は、その例外は 回線にとって致命的です。 ハンドラーのコードまたはメソッドで例外が許容されるようにするには、エラー処理とログを含む 1 つ以上の try-catch ステートメントでコードをラップします。

ユーザーが切断し、フレームワークで回線の状態がクリーンアップされていることが原因で回線が終了すると、フレームワークによって回線の DI スコープが破棄されます。 スコープが破棄されると、System.IDisposable を実装するサーキットスコープの DI サービスはすべて破棄されます。 破棄中にいずれかの DI サービスでハンドルされない例外がスローされると、フレームワークによって例外がログに記録されます。 詳細については、「ASP.NET Core Blazor の依存関係の挿入」を参照してください。

カスタム サービスのユーザーをキャプチャするためのサーバー側回線ハンドラー

AuthenticationStateProvider からユーザーをキャプチャして、サービスでそのユーザーを設定するには、CircuitHandler を使います。 詳細とコード例については、「サーバーサイド ASP.NET CoreBlazor のセキュリティに関するその他のシナリオ」を参照してください。

対話型サーバー コンポーネントが残っていない場合に回線を閉じる

対話型サーバー コンポーネントでは、ブラウザーとのリアルタイム接続 (回線) を使用して、Web UI イベントを処理します。 ルートの対話型サーバー コンポーネントがレンダリングされる際、回線およびその関連付けられた状態が作成されます。 ページ上に対話型サーバー コンポーネントが残っていない場合、回線は閉じられ、サーバー リソースは解放されます。

Razor コンポーネント内の IHttpContextAccessor/HttpContext

有効な HttpContext が使用できないため、対話型レンダリングでは IHttpContextAccessor を避ける必要があります。

IHttpContextAccessor は、サーバー上で静的にレンダリングされるコンポーネントに対して使用することができます。 ただし、可能であれば使用を避けることをお勧めします。

HttpContext は、App コンポーネント (Components/App.razor) 内のヘッダーやその他のプロパティの検査や変更などの一般的なタスク用に、"静的にレンダリングされたルート コンポーネント" 内でのみ、カスケード パラメーターとして使用できます。 対話型レンダリングの場合、この値は常に null です。

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

対話型コンポーネント内で HttpContext が必要なシナリオの場合は、サーバーから永続的なコンポーネントの状態を介してデータを取り込むことをお勧めします。 詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。

サーバー側 Blazor アプリの Razor コンポーネントでは、IHttpContextAccessor/HttpContext を、直接にも間接的にも使用しないでください。 Blazor アプリは、ASP.NET Core パイプラインのコンテキストの外部で実行されます。 HttpContext は、IHttpContextAccessor 内で使用できるとは限りません。また、HttpContext は、Blazor アプリを開始したコンテキストが保持されることも保証されません。

アプリの初期レンダリング中にルート コンポーネント パラメーターを使って要求の状態を Blazor アプリに渡すことをお勧めします。 または、ルート コンポーネントの初期化ライフサイクル イベントにおいてアプリでスコープ サービスにデータをコピーすることで、アプリ全体で使用することもできます。 詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。

サーバー側 Blazor のセキュリティでの重要な側面は、特定の回線に接続されているユーザーは Blazor 回線が確立された後のある時点で更新される可能性がありますが、IHttpContextAccessor更新されないということです。 カスタム サービスでこの状況に対処する方法の詳細については、「サーバーサイド ASP.NET Core Blazor のセキュリティに関するその他のシナリオ」を参照してください。

その他のサーバー側リソース