次の方法で共有


ASP.NET Core Razor コンポーネントのプリレンダリング

注記

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

重要

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

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

この記事では、Razor でのサーバーでレンダリングされるコンポーネントの Blazor Web App コンポーネント プリレンダリング シナリオについて説明します。

プリレンダリングは、レンダリングされたコントロールのイベント ハンドラーを有効にせずに、最初にサーバーにページ コンテンツをレンダリングするプロセスです。 サーバーは、最初の要求に応じてできるだけ早くページの HTML UI を出力します。これにより、アプリはユーザーに対してより応答性が高くなります。 プリレンダリングでは、検索エンジンによってページ ランクの計算に使用される初期 HTTP 応答のコンテンツをレンダリングすることで、検索エンジンの最適化 (SEO) を向上させることもできます。

プリレンダリングされた状態を保持する

プリレンダリングされた状態を保持しないと、プリレンダリング中に使用された状態は失われ、アプリが完全に読み込まれたときに再作成する必要があります。 状態が非同期的に作成される場合、コンポーネントの再レンダリングによってプリレンダリングされたUIが置き換えられる際に、UIが一時的にちらつくことがあります。

次の PrerenderedCounter1 カウンター コンポーネントについて検討します。 コンポーネントは、OnInitializedライフサイクル メソッドでのプリレンダリング中に初期ランダム カウンター値を設定します。 クライアントへの SignalR 接続が確立されると、コンポーネントが再レンダリングされ、OnInitialized の 2 回目の実行時に初期カウント値が置き換えられます。

PrerenderedCounter1.razor:

@page "/prerendered-counter-1"
@rendermode @(new InteractiveServerRenderMode(prerender: true))
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;

    protected override void OnInitialized()
    {
        currentCount = Random.Shared.Next(100);
        Logger.LogInformation("currentCount set to {Count}", currentCount);
    }

    private void IncrementCount() => currentCount++;
}

アプリを実行し、コンポーネントのログを調査します。 以下に、出力の例を示します。

注記

アプリが対話型ルーティング 採用し、ページに内部 拡張ナビゲーション経由で到達した場合、プリレンダリングは行われません。 したがって、次の出力を表示するには、PrerenderedCounter1 コンポーネントのページ全体の再読み込みを行う必要があります。 詳細については、「対話型ルーティングとプリレンダリングの」セクションを参照してください。

info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 41
info: BlazorSample.Components.Pages.PrerenderedCounter1[0]
currentCount set to 92

最初のログ カウントはプリレンダリング中に生じます。 コンポーネントが再レンダリングされると、プリレンダリング後にカウントが再び設定されます。 カウントが 41 から 92 に更新されるときにも、UI にちらつきが生じます。

プリレンダリング中にカウンターの初期値を保持するために、Blazor では、PersistentComponentState サービス (さらに、Razor Pages または MVC アプリのページまたはビューに埋め込まれたコンポーネントの場合は、コンポーネントの状態保持タグ ヘルパー) を使用してプリレンダリングされたページ内の状態の永続化をサポートしています。

プリレンダリングされた状態を保持するには、 [SupplyParameterFromPersistentComponentState] 属性を使用してプロパティの状態を保持します。 この属性を持つプロパティは、プリレンダリング中に PersistentComponentState サービスを使用して自動的に永続化されます。 状態は、コンポーネントが対話形式でレンダリングされるか、サービスがインスタンス化されるときに取得されます。

既定では、プロパティは既定の設定で System.Text.Json シリアライザーを使用してシリアル化されます。 シリアル化はトリマー使用に対する安全性がなく、使用される型の保存が必要です。 詳しくは、「ASP.NET Core Blazor 用のトリマーを構成する」をご覧ください。

次のカウンター コンポーネントは、プリレンダリング中にカウンター状態を保持し、コンポーネントを初期化する状態を取得します。

  • [SupplyParameterFromPersistentComponentState]属性は、CounterState型 (State) に適用されます。
  • カウンターの状態は、nullOnInitializedする際に割り当てられ、コンポーネントがインタラクティブにレンダリングされる際に自動的に復元されます。

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@inject ILogger<PrerenderedCounter2> Logger

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @State?.CurrentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [SupplyParameterFromPersistentComponentState]
    public CounterState? State { get; set; }

    protected override void OnInitialized()
    {
        if (State is null)
        {
            State = new() { CurrentCount = Random.Shared.Next(100) };
            Logger.LogInformation("CurrentCount set to {Count}", 
                State.CurrentCount);
        }
        else
        {
            Logger.LogInformation("CurrentCount restored to {Count}", 
                State.CurrentCount);
        }
    }

    private void IncrementCount()
    {
        if (State is not null)
        {
            State.CurrentCount++;
        }
    }

    public class CounterState
    {
        public int CurrentCount { get; set; }
    }
}

コンポーネントが実行されると、プリレンダリング中に CurrentCount が 1 回だけ設定されます。 この値は、コンポーネントが再レンダリングされると復元されます。 以下に、出力の例を示します。

注記

アプリが対話型ルーティング 採用し、ページに内部 拡張ナビゲーション経由で到達した場合、プリレンダリングは行われません。 したがって、次の出力を表示するには、コンポーネントのページ全体の再読み込みを実行する必要があります。 詳細については、「対話型ルーティングとプリレンダリングの」セクションを参照してください。

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
CurrentCount restored to 96

次の例では、同じ型の複数のコンポーネントの状態をシリアル化します。

  • [SupplyParameterFromPersistentComponentState]属性で注釈が付けられたプロパティは、プリレンダリング中にシリアル化および逆シリアル化されます。
  • @key ディレクティブ属性は、状態がコンポーネント インスタンスに正しく関連付けられていることを確認するために使用されます。
  • Element プロパティは、クエリ パラメーターやフォーム データに対する null 参照の回避方法と同様に、null 参照の例外を回避するために、OnInitialized ライフサイクル メソッドで初期化されます。

PersistentChild.razor:

<div>
    <p>Current count: @Element.CurrentCount</p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</div>

@code {
    [SupplyParameterFromPersistentComponentState]
    public State Element { get; set; }

    protected override void OnInitialized()
    {
        Element ??= new State();
    }

    private void IncrementCount()
    {
        Element.CurrentCount++;
    }

    private class State
    {
        public int CurrentCount { get; set; }
    }
}

Parent.razor:

@page "/parent"

@foreach (var element in elements)
{
    <PersistentChild @key="element.Name" />
}

依存関係挿入サービスの状態をシリアル化する次の例では、

  • [SupplyParameterFromPersistentComponentState]属性で注釈が付けられたプロパティは、プリレンダリング中にシリアル化され、アプリが対話型になると逆シリアル化されます。
  • AddPersistentService メソッドは、永続化のためにサービスを登録するために使用されます。 レンダリング モードはサービスの種類から推論できないため、レンダリング モードが必要です。 次のいずれかの値を使用します。
    • RenderMode.Server: サービスは、対話型サーバー レンダリング モードで使用できます。
    • RenderMode.Webassembly: サービスは対話型 Webassembly レンダリング モードで使用できます。
    • RenderMode.InteractiveAuto: コンポーネントがいずれかのモードでレンダリングされる場合、サービスは Interactive Server レンダリング モードと Interactive Webassembly レンダリング モードの両方で使用できます。
  • サービスは対話型レンダリング モードの初期化中に解決され、 [SupplyParameterFromPersistentComponentState] 属性で注釈が付けられたプロパティは逆シリアル化されます。

注記

スコープ付きサービスの永続化のみがサポートされています。

CounterService.cs:

public class CounterService
{
    [SupplyParameterFromPersistentComponentState]
    public int CurrentCount { get; set; }

    public void IncrementCount()
    {
        CurrentCount++;
    }
}

Program.csの場合:

builder.Services.AddPersistentService<CounterService>(RenderMode.InteractiveAuto);

シリアル化されたプロパティは、実際のサービス インスタンスから識別されます。

  • この方法では、抽象化を永続的なサービスとしてマークできます。
  • 実際の実装を内部型または異なる型にできます。
  • さまざまなアセンブリの共有コードをサポートします。
  • 結果として、各インスタンスで同じプロパティが公開されます。

[SupplyParameterFromPersistentComponentState]属性を使用して状態を永続化するために宣言型モデルを使用する代わりに、PersistentComponentState サービスを直接使用することもできます。これによって、複雑な状態永続化シナリオの柔軟性が向上します。 プリレンダリング中にコンポーネントの状態を保持するコールバックを登録する PersistentComponentState.RegisterOnPersisting を呼び出します。 状態は、コンポーネントが対話形式でレンダリングされるときに取得されます。 アプリのシャットダウン中に競合状態になる可能性を回避するために、初期化コードの最後に呼び出しを行います。

次のカウンター コンポーネントでは、プリレンダリング中にカウンターの状態を保持し、コンポーネントを初期化するためにその状態を取得します。

PrerenderedCounter3.razor:

@page "/prerendered-counter-3"
@implements IDisposable
@inject ILogger<PrerenderedCounter3> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 3</PageTitle>

<h1>Prerendered Counter 3</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    private void IncrementCount() => currentCount++;

    void IDisposable.Dispose() => persistingSubscription.Dispose();
}

コンポーネントが実行されると、プリレンダリング中に currentCount が 1 回だけ設定されます。 この値は、コンポーネントが再レンダリングされると復元されます。 以下に、出力の例を示します。

注記

アプリが対話型ルーティング 採用し、ページに内部 拡張ナビゲーション経由で到達した場合、プリレンダリングは行われません。 したがって、次の出力を表示するには、コンポーネントのページ全体の再読み込みを実行する必要があります。 詳細については、「対話型ルーティングとプリレンダリングの」セクションを参照してください。

info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter3[0]
currentCount restored to 96

プリレンダリングされた状態を保持するには、PersistentComponentState サービスを使用して、どの状態を永続化するかを決定します。 PersistentComponentState.RegisterOnPersisting は、プリレンダリング中にコンポーネントの状態を保持するコールバックを登録します。 状態は、コンポーネントが対話形式でレンダリングされるときに取得されます。 アプリのシャットダウン中に競合状態になる可能性を回避するために、初期化コードの最後に呼び出しを行います。

次のカウンター コンポーネントでは、プリレンダリング中にカウンターの状態を保持し、コンポーネントを初期化するためにその状態を取得します。

PrerenderedCounter2.razor:

@page "/prerendered-counter-2"
@implements IDisposable
@inject ILogger<PrerenderedCounter2> Logger
@inject PersistentComponentState ApplicationState

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override void OnInitialized()
    {
        if (!ApplicationState.TryTakeFromJson<int>(
            nameof(currentCount), out var restoredCount))
        {
            currentCount = Random.Shared.Next(100);
            Logger.LogInformation("currentCount set to {Count}", currentCount);
        }
        else
        {
            currentCount = restoredCount!;
            Logger.LogInformation("currentCount restored to {Count}", currentCount);
        }

        // Call at the end to avoid a potential race condition at app shutdown
        persistingSubscription = ApplicationState.RegisterOnPersisting(PersistCount);
    }

    private Task PersistCount()
    {
        ApplicationState.PersistAsJson(nameof(currentCount), currentCount);

        return Task.CompletedTask;
    }

    void IDisposable.Dispose() => persistingSubscription.Dispose();

    private void IncrementCount() => currentCount++;
}

コンポーネントが実行されると、プリレンダリング中に currentCount が 1 回だけ設定されます。 この値は、コンポーネントが再レンダリングされると復元されます。 以下に、出力の例を示します。

注記

アプリが対話型ルーティング 採用し、ページに内部 拡張ナビゲーション経由で到達した場合、プリレンダリングは行われません。 したがって、次の出力を表示するには、コンポーネントのページ全体の再読み込みを実行する必要があります。 詳細については、「対話型ルーティングとプリレンダリングの」セクションを参照してください。

info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount set to 96
info: BlazorSample.Components.Pages.PrerenderedCounter2[0]
currentCount restored to 96

プリレンダリング中に使用されたのと同じ状態でコンポーネントを初期化することにより、負荷の高い初期化ステップが 1 回だけ実行されます。 レンダリングされた UI もプリレンダリングされた UI に一致するので、ブラウザーでちらつきは発生しません。

永続化されたプリレンダリングされた状態はクライアントに転送され、そこでコンポーネントの状態を復元するために使用されます。 クライアント側のレンダリング (CSR、 InteractiveWebAssembly) 中に、データはブラウザーに公開され、機密情報を含めてはなりません。 対話型のサーバー側レンダリング (対話型 SSR、 InteractiveServer) 中に、コア データ保護 ASP.NET データが安全に転送されるようにします。 InteractiveAutoレンダリング モードは WebAssembly とサーバーの対話機能を組み合わせたものなので、CSR の場合と同様に、ブラウザーへのデータ公開を考慮する必要があります。

ページとビューに埋め込まれたコンポーネント (Razor Pages/MVC)

Razor Pages または MVC アプリのページまたはビューに埋め込まれたコンポーネントに対しては、アプリのレイアウトの終了 タグ内に、<persist-component-state /> HTML タグを含む</body>を追加する必要があります。 これは、Razor Pages アプリと MVC アプリでのみ必要です。 詳細については、「ASP.NET Core でのコンポーネントの状態保持タグ ヘルパー」を参照してください。

Pages/Shared/_Layout.cshtml:

<body>
    ...

    <persist-component-state />
</body>

対話型ルーティングとプリレンダリング

Routes コンポーネントでレンダリング モードが定義されていない場合、アプリはページ単位/コンポーネントごとの対話機能とナビゲーションを使用します。 ページ単位またはコンポーネント単位のナビゲーションを使用すると、内部†ナビゲーションは、アプリが対話型になった後 拡張ルーティング によって処理されます。 †この文脈での内部 とは、ナビゲーション イベントの URL の宛先がアプリ内の Blazor エンドポイントであることを意味します。

PersistentComponentState サービスは、初期ページ読み込みでのみ機能し、内部拡張ページ ナビゲーション イベントでは機能しません。

永続的なコンポーネントの状態を利用するページへの完全な (拡張されていない) ナビゲーションをアプリが実行すると、永続化された状態が対話型になったときにアプリで使用できるようになります。

対話型回線が既に確立されていて、永続的なコンポーネントの状態を利用するページへの拡張ナビゲーションが実行されている場合、コンポーネントがを使用するために、既存の回線では状態 使用できなくなります。 内部ページ要求のプリレンダリングはなく、PersistentComponentState サービスは拡張ナビゲーションが発生したことを認識していません。 既存の回線で既に実行されているコンポーネントに状態更新を配信するメカニズムはありません。 その理由は、Blazor はランタイムの初期化時にサーバーからクライアントへの状態の受け渡しのみをサポートし、ランタイムの開始後にはサポートされないためです。

このシナリオに対処するための Blazor フレームワークの追加作業は、.NET 10 (2025 年 11 月) で検討されています。 でサポートされていない回避策の詳細とコミュニティの説明については、「拡張ページナビゲーション (dotnet/aspnetcore #51584)での永続的なコンポーネントの状態のサポート」を参照してください。 ‡サポートされていない回避策は、Blazor アプリで使用するために Microsoft によって承認されていません。 サードパーティ製のパッケージ、アプローチ、コードは、ご自身の責任で使用してください。

パフォーマンスを低下させる一方で、内部ページ要求の による状態の読み込みの問題を回避する拡張ナビゲーションを無効にする方法については、コア ルーティングとナビゲーションASP.NET で説明します。

プリレンダリングのガイドライン

プリレンダリング ガイダンスは、Blazor ドキュメント内で主題別に整理されています。 次のリンクは、主題別に設定された、ドキュメント全体のすべてのプリレンダリング ガイダンスを含んでいます。

.NET 7 以前については、「 Blazor WebAssembly セキュリティの追加シナリオ: 認証を使用したプリレンダリング」を参照してください。 このセクションのコンテンツを表示した後、ドキュメント記事のバージョン セレクタードロップダウンを最新の .NET リリース バージョンにリセットして、ドキュメント ページが後続のアクセス時に最新リリースに読み込まれるようにします。