ASP.NET Core の Razor クラス ライブラリ (RCL) と静的サーバー側レンダリング (静的 SSR)

この記事では、静的サーバー側レンダリング (静的 SSR) のサポートを検討しているコンポーネント ライブラリ作成者のためのガイダンスを提供します。

Blazor は、オープンソースと商用コンポーネント ライブラリ (正式には Razor クラス ライブラリ (RCL) と呼ばれます) のエコシステムを開発することをお勧めします。 開発者は、自社内のアプリ間でプライベートにコンポーネントを共有するための再利用可能なコンポーネントを作成することもできます。 コンポーネントは、理想的には、可能な限り多くのホスティング モデルおよびレンダリング モードと互換性があるように開発します。 静的 SSR によって加わる制限によって、対話型レンダリング モードよりサポートが困難になる可能性があります。

静的 SSR の機能と制限について理解する

静的 SSR モードでは、サーバーが着信した HTTP 要求を処理するときにコンポーネントが実行されます。 Blazor は、応答に含まれる HTML としてコンポーネントをレンダリングします。 応答が送信されると、サーバー側のコンポーネントとレンダラーの状態は破棄されるため、残っているのはブラウザーの HTML だけです。

このモードの長所は、コンポーネントの状態を保持するために継続的なサーバー リソースが必要なく、ブラウザーとサーバーの間で継続的な接続を維持する必要がなく、ブラウザーで WebAssembly ペイロードが必要ないため、安価でスケーラブルなホスティングであることです。

既存のすべてのコンポーネントを静的 SSR で引き続き使用できます。 一方、このモードの短所は、次の理由により、@onclick† などのイベント ハンドラーを実行できないことです。

  • それを実行するための .NET コードがブラウザーにありません。
  • サーバーは、イベント ハンドラーの実行または同じコンポーネント インスタンスの再レンダリングに必要なコンポーネントとレンダラーの状態を、即座に破棄します。

† フォームの @onsubmit イベント ハンドラーは特別な例外で、レンダリング モードに関係なく、常に機能します。

これは、Blazor 回線または .NET WebAssembly ランタイムが開始される前の、プリレンダリングの間のコンポーネントの動作と同じです。

読み取り専用の DOM コンテンツを生成することが唯一の役割であるコンポーネントの場合は、静的 SSR のこのような動作でもまったく問題ありません。 一方、ライブラリに対話型コンポーネントを含める場合は、ライブラリ作成者はどのようなアプローチにするか検討する必要があります。

コンポーネント作成者のためのオプション

主に 3 つの方法があります。

  • 対話型の動作を使わない (基本)

    読み取り専用 DOM コンテンツの生成が唯一の役割のコンポーネントの場合、開発者は特別なアクションを行う必要はありません。 これらのコンポーネントは、どのレンダリング モードでも自然に動作します。

    例 :

    • ユーザーに対応するデータを読み込み、写真、役職、その他の詳細を含むスタイル化された UI にそれをレンダリングする "ユーザー カード" コンポーネント。
    • HTML の <video> 要素のラッパーとして機能し、Razor コンポーネントでいっそう使いやすくする "ビデオ" コンポーネント。
  • 対話型レンダリングを必須にする (基本)

    コンポーネントが対話型レンダリングのみで使われることを必須にできます。 このようにすると、コンポーネントの適応性は制限されますが、任意のイベント ハンドラーに自由に依存できることを意味します。 その場合でも、特定の @rendermode を宣言することは避け、ライブラリを使うアプリ作成者が選択できるようにする必要があります。

    例 :

    • ユーザーがビデオのセグメントをスプライスして順序を変更できるビデオ編集コンポーネント。 これらの編集操作をプレーンな HTML ボタンとフォームの投稿で表す方法があったとしても、真の対話機能がなければ、ユーザー エクスペリエンスは受け入れられません。
    • 他のユーザーのアクティビティをリアルタイムで表示する必要がある共同作業ドキュメント エディター。
  • 対話型動作を使うが、静的 SSR と段階的な強化に対応するように設計する (高度)

    多くの対話型動作は、HTML の機能だけを使って実装できます。 HTML と CSS についてよく理解していれば、静的 SSR で動作する機能の有用なベースラインを生成できることがよくあります。 さらに高度なオプションの動作を実装するイベント ハンドラーを宣言することもできます。これは、対話型レンダリング モードでのみ機能します。

    例 :

    • グリッド コンポーネント。 静的 SSR では、コンポーネントはデータの表示とページ間の移動 (<a> リンクを使用して実装) のみをサポートできます。 対話型レンダリングでコンポーネントを使うと、ライブの並べ替えとフィルター処理を追加できます。
    • タブセット コンポーネント。 タブ間のナビゲーションが <a> リンクを使って実現されていて、状態が URL クエリ パラメーターのみで保持されている限り、コンポーネントは @onclick なしで機能できます。
    • 高度なファイル アップロード コンポーネント。 静的 SSR では、コンポーネントはネイティブ <input type=file> として動作できます。 対話型レンダリングでコンポーネントを使うと、アップロードの進行状況を表示することもできます。
    • 株価ティッカー。 静的 SSR でのコンポーネントは、HTML がレンダリングされた時点での株価情報を表示できます。 対話型レンダリングでコンポーネントを使うと、株価をリアルタイムで更新できます。

これらのどの戦略でも、JavaScript を使って対話型機能を実装することもできます。

再利用可能な Razor コンポーネントの作成者は、これらのアプローチのいずれかを選ぶ場合、コストと利益のトレードオフを行う必要があります。 コンポーネントで静的 SSR を含むすべてのレンダリング モードをサポートすると、いっそう便利になり、潜在的なユーザー ベースがさらに広がります。 ただし、各レンダリング モードをサポートし、最大限に活用するコンポーネントでは、設計と実装に必要な作業が増えます。

@rendermode ディレクティブを使うべきとき

ほとんどの場合、再利用可能なコンポーネントの作成者は、対話機能が必要な場合であっても、レンダリング モードを指定しないようにする必要があります。 これは、アプリで InteractiveServerInteractiveWebAssembly、またはその両方 (InteractiveAuto) のサポートが有効になっているかどうかが、コンポーネントの作成者にはわからないためです。 コンポーネントの作成者は、@rendermode を指定しないことで、選択をアプリの開発者に委ねることができます。

対話機能が必要であるとコンポーネント作成者が考える場合でも、アプリ作成者は静的 SSR を使うだけで十分であると考える可能性があります。 たとえば、ドラッグとズームの対話機能を持つマップ コンポーネントでは、対話機能が必要と思われるかもしれません。 しかし、シナリオによっては、静的マップ イメージのレンダリングを呼び出すだけでよく、ドラッグとズーム機能は使わなくて済む場合があります。

再利用可能なコンポーネントの作成者がコンポーネントで @rendermode ディレクティブをどうしても使わなければならない唯一の理由は、実装が基本的に 1 つの特定のレンダリング モードに結び付けられていて、別のモードで使うと確実にエラーが発生する場合です。 Windows または Linux 固有の API を使ってホスト OS と直接対話することが主な目的であるコンポーネントについて考えてみてください。 WebAssembly でこのようなコンポーネントを使うのは不可能な場合があります。 その場合は、コンポーネントに対して @rendermode InteractiveServer を宣言する十分な理由になります。

ストリーミング レンダリング

再利用可能な Razor コンポーネントでは、ストリーミング レンダリングのための @attribute [StreamRendering] を自由に宣言できます ([StreamRendering] 属性 API)。 これにより、静的 SSR の間に UI の増分更新が行われます。 同じデータ読み込みパターンによって、[StreamRendering] 属性の有無に関係なく、対話型レンダリングの間に増分 UI 更新が生成されるため、コンポーネントはすべてのケースで正しく動作できます。 静的 SSR のストリーミングがサーバーで抑制されている場合でも、コンポーネントは正しい最終状態をレンダリングします。

再利用可能な Razor コンポーネントでは、リンクと拡張ナビゲーションを使用できます。 HTML の <a> タグでは、対話型 Router コンポーネントであってもなくても、また DOM の先祖レベルで拡張ナビゲーションが有効または無効になっているかどうかに関わらず、同じ動作が生成されます。

異なるレンダリング モードでのフォームの使用

フォーム (<form> または <EditForm>) は、静的と対話型どちらのレンダリング モードでも同じように動作するよう実装できるので、再利用可能な Razor コンポーネントはこれらのフォームを含むことができます。

次の例を考えてみましょう。

<EditForm Enhance FormName="NewProduct" Model="Model" OnValidSubmit="SaveProduct">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <p><label>Name: <InputText @bind-Value="Item.Name" /></label></p>

    <button type="submit">Submit</button>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private Product? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    private async Task Save()
    {
        ...
    }
}

EnhanceFormNameSupplyParameterFromFormAttribute の各 API は静的 SSR の間にのみ使われ、対話型レンダリング中は無視されます。 フォームは、対話型 SSR と静的 SSR のどちらの間も正しく動作します。

静的 SSR に固有の API を回避する

HttpContext は静的 SSR 中にのみ使用できるため、レンダリング モード間で動作するコンポーネントを作成する場合は、HttpContext に依存しないでください。 対話型レンダリング中は、処理されているアクティブな HTTP 要求はないため、HttpContext API を使っても意味がありません。 HTTP 状態コードの設定や HTTP 応答への書き込みについて考えても無意味です。

次のように、使用可能な場合は再利用可能なコンポーネントで HttpContext を受け取ってもかまいません。

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

この値は、対話型レンダリング中は null であり、静的 SSR の間にだけ設定されます。

多くの場合、HttpContext を使うより良い選択肢があります。 現在の URL を把握したり、リダイレクトを実行したりする必要がある場合、NavigationManager の API はすべてのレンダリング モードで動作します。 ユーザーの認証状態を知る必要がある場合は、HttpContext.User を使うより、Blazor の AuthenticationStateProvider サービス (AuthenticationStateProvider ドキュメント) を使ってください。