次の方法で共有


ASP.NET Core Razor コンポーネントのライフサイクル

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

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。

重要

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

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

この記事では、ASP.NET Core Razor コンポーネントのライフサイクルと、ライフサイクル イベントの使用方法について説明します。

ライフサイクル イベント

Razor コンポーネントは、一連の同期および非同期のライフサイクル メソッド内の Razor コンポーネント ライフサイクル イベントを処理します。 ライフサイクル メソッドをオーバーライドして、コンポーネントの初期化およびレンダリング中にコンポーネントで追加の操作を実行できます。

この記事では、複雑なフレームワーク ロジックを明確にするために、コンポーネント ライフサイクル イベント処理を簡略化して説明しますが、何年にもわたって行われてきた変更をすべて取り上げるわけではありません。 カスタム イベント処理をComponentBaseのライフサイクル イベント処理と統合するには、にアクセスする必要がある場合があります。 この参照ソースのコード コメントには、この記事や API ドキュメントには記載されていないライフサイクル イベント処理に関する追加の注釈が含まれています。

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

次の簡略化された図は、Razor コンポーネント ライフサイクル イベント処理を示しています。 ライフサイクル イベントに関連付けられている C# メソッドは、この記事の次のセクションで例と共に定義されています。

コンポーネント ライフサイクル イベント:

  1. 要求に対してコンポーネントが初めてレンダリングされる場合は、次のようにします。
    • コンポーネントのインスタンスを作成します。
    • プロパティの挿入を実行します。
    • OnInitialized{Async} を呼び出します。 未完了の Task が返された場合は、Task を待機してから、コンポーネントが再レンダリングされます。 同期メソッドは、非同期メソッドの前に呼び出されます。
  2. OnParametersSet{Async} を呼び出します。 未完了の Task が返された場合は、Task を待機してから、コンポーネントが再レンダリングされます。 同期メソッドは、非同期メソッドの前に呼び出されます。
  3. すべての同期作業に対してレンダリングし、Task を完了します。

ライフサイクル イベントで実行される非同期アクションは、コンポーネントのレンダリングやデータの表示を遅らせる可能性があります。 詳細については、この記事で後述する「render での不完全な非同期アクションの処理」セクションを参照してください。

レンダリングはどの子コンポーネントが存在するかを決定するものであるため、親コンポーネントは子コンポーネントより先にレンダリングされます。 同期的な親コンポーネント初期化を使用する場合は、親の初期化が最初に完了することが保証されます。 非同期の親コンポーネント初期化を使用する場合、親と子のコンポーネント初期化の完了順序は、実行されている初期化コードに依存するため、決定することができません。

Razor の Blazor コンポーネントにおけるコンポーネントライフサイクルイベント

DOM イベントの処理:

  1. イベント ハンドラーが実行されます。
  2. 未完了の Task が返された場合は、Task を待機してから、コンポーネントが再レンダリングされます。
  3. すべての同期作業に対してレンダリングし、Task を完了します。

DOM イベントの処理

Render のライフサイクル:

  1. 次の条件が両方とも満たされる場合は、コンポーネントに対するさらなるレンダリング操作は避けてください。
    • 最初のレンダリングではありません。
    • ShouldRender は、false を返します。
  2. レンダリング ツリーの差分を作成し、コンポーネントをレンダリングします。
  3. DOM が更新されるのを待機します。
  4. OnAfterRender{Async} を呼び出します。 同期メソッドは、非同期メソッドの前に呼び出されます。

レンダーライフサイクル

Developer によって StateHasChanged の呼び出しが行われると、再レンダリングが実行されます。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。

プリレンダリング中の休止

サーバー側 Blazor アプリでは、プリレンダリングは 休止を待機します。つまり、レンダリング ツリー内のすべてのコンポーネントがレンダリングを完了するまで、コンポーネントはレンダリングされません。 静止すると、コンポーネントが初期化やその他のライフサイクル メソッド中に実行時間の長いタスクを実行すると、レンダリングが著しく遅延し、ユーザー エクスペリエンスが低下する可能性があります。 詳細については、この記事で後述する「render での不完全な非同期アクションの処理」セクションを参照してください。

パラメーターが設定されるタイミング (SetParametersAsync)

SetParametersAsync により、レンダリング ツリーのコンポーネントの親によって、またはルート パラメーターから指定されたパラメーターが設定されます。

メソッドのParameterViewパラメーターには、が呼び出されるたびに、コンポーネントごとのSetParametersAsync値のセットが含まれます。 開発者のコードでは、SetParametersAsync メソッドをオーバーライドすることによって、ParameterView のパラメーターを直接操作できます。

SetParametersAsync の既定の実装では、[Parameter] に対応する値を持つ [CascadingParameter] または ParameterView 属性を使って、各プロパティの値を設定します。 対応する値が ParameterView 内にないパラメーターは、変更されないままになります。

一般に、await base.SetParametersAsync(parameters); をオーバーライドするときはコードで基底クラス メソッド (SetParametersAsync) を呼び出す必要があります。 高度なシナリオでは、開発者コードで、基底クラス メソッドを呼び出さず、入ってくるパラメーターの値を必要なあらゆる方法で解釈できます。 たとえば、受信したパラメーターをクラスのプロパティに割り当てる必要はありません。 ただし、基底クラス メソッドを呼び出さずにコードを構築するとき、ComponentBase 参照ソースを参照する必要があります。他のライフサイクル メソッドを呼び出し、複雑な方法でのレンダリングをトリガーするためです。

通常、.NET 参照ソースへのドキュメント リンクを使用すると、リポジトリの既定のブランチが読み込まれます。このブランチは、.NET の次回リリースに向けて行われている現在の開発を表します。 特定のリリースのタグを選択するには、[Switch branches or tags](ブランチまたはタグの切り替え) ドロップダウン リストを使います。 詳細については、「ASP.NET Core ソース コードのバージョン タグを選択する方法」 (dotnet/AspNetCore.Docs #26205) を参照してください。

ComponentBase.SetParametersAsync の初期化およびレンダリング ロジックに依存するが、受信パラメータを処理しない場合は、空の ParameterView を基底クラス メソッドに渡すオプションがあります。

await base.SetParametersAsync(ParameterView.Empty);

開発者コード内にイベントハンドラーがある場合、破棄時にこれらを解除します。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。

次の例では、ParameterView.TryGetValue のルート パラメーターの解析が成功した場合、Param によって value パラメーターの値が Param に代入されます。 valuenull でなければ、コンポーネントによってその値が表示されます。

ルート パラメーターの照合では大文字と小文字が区別されませんが、ルート テンプレートでは TryGetValue によってパラメーター名が大文字と小文字を区別して照合されます。 次の例では、/{Param?} で値を取得するために、ルート テンプレートで TryGetValue ではなく /{param?} を使用する必要があります。 このシナリオで /{param?} が使用される場合、TryGetValue から false が返され、message はどちらの message 文字列にも設定されません。

SetParamsAsync.razor:

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async"
@page "/set-params-async/{Param}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}

コンポーネントの初期化 (OnInitialized{Async})

OnInitializedOnInitializedAsync は、コンポーネント インスタンスの全有効期間において、コンポーネントの初期化にのみ使用されます。 パラメーター値とパラメーター値の変更はこれらのメソッドで実行される初期化に影響を及ぼさないこととします。 たとえば、コンポーネントの有効期間を通じて変更されず、パラメーター値に依存しないドロップダウン リストへの静的オプションの読み込みは、これらのライフサイクル メソッドのいずれかで実行されます。 パラメーター値またはパラメーター値の変更がコンポーネントの状態に影響する場合には、代わりに OnParametersSet{Async} を使用します。

これらのメソッドは、コンポーネントが SetParametersAsync により初期パラメーターを受け取った後で初期化されるときに呼び出されます。 同期メソッドは、非同期メソッドの前に呼び出されます。

同期的な親コンポーネント初期化を使用する場合は、親の初期化は子コンポーネントの初期化よりも前に完了することが保証されます。 非同期の親コンポーネント初期化を使用する場合、親と子のコンポーネント初期化の完了順序は、実行されている初期化コードに依存するため、決定することができません。

同期操作の場合は、OnInitialized をオーバーライドします。

OnInit.razor:

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

非同期操作を実行するには、OnInitializedAsync をオーバーライドし、await 演算子を使用します。

protected override async Task OnInitializedAsync()
{
    await ...
}

カスタム基底クラスをカスタム初期化ロジックで使用する場合は、基底クラスで OnInitializedAsync を呼び出します。

protected override async Task OnInitializedAsync()
{
    await ...

    await base.OnInitializedAsync();
}

カスタム基底クラスをカスタム ロジックで使用しない限り、ComponentBase.OnInitializedAsync を呼び出す必要はありません。 詳細については、「基底クラスのライフサイクル メソッド」セクションを参照してください。

コンポーネントは、OnInitializedAsync が潜在的に不完全な Task を待機しているときに、レンダリングに正しい状態にあることを確認する必要があります。 メソッドが不完全な Taskを返す場合、同期的に完了するメソッドの部分は、コンポーネントをレンダリングの有効な状態のままにする必要があります。 詳細については、ASP.NET Core Blazor 同期コンテキストASP.NET Core Razor コンポーネントの破棄 の概要に関する解説を参照してください。

サーバーでコンテンツをプリレンダリングするアプリは、BlazorOnInitializedAsync2回呼び出します。

  • コンポーネントが最初にページの一部として静的にレンダリングされるときに 1 回。
  • 2 回目はブラウザーがコンポーネントをレンダリングするとき。

プリレンダリングする際に OnInitializedAsync で開発者コードが 2 回実行されないようにするには、「プリレンダリング後のステートフル再接続」セクションを参照してください。 このセクションの内容は、Blazor Web App とステートフル SignalR の 再接続 に焦点を当てています。 プリレンダリング中に初期化コードの実行中の状態を保持するには、「ASP.NET Core Razor コンポーネントのプリレンダリングを行う」をご覧ください。

プリレンダリングする際に OnInitializedAsync で開発者コードが 2 回実行されないようにするには、「プリレンダリング後のステートフル再接続」セクションを参照してください。 このセクションのコンテンツでは、Blazor Server およびステートフルな SignalR の "再接続" に焦点を当てていますが、ホストされた ソリューション (Blazor WebAssembly) でのプリレンダリングのシナリオでは、開発者コードを 2 回実行しないようにするための同様の条件とアプローチが必要です。WebAssemblyPrerendered 初期化コードの実行中にプリレンダリングしながら状態を保持するには、Razor方法を参照してください。

Blazor アプリがプリレンダリングされている間、JavaScript の呼び出し (JS 相互運用) などの特定のアクションは不可能です。 コンポーネントは、プリレンダリング時に異なるレンダリングが必要になる場合があります。 詳細については、「JavaScript 相互運用を使用したプリレンダリング」セクションを参照してください。

開発者コード内にイベントハンドラーがある場合、破棄時にこれらを解除します。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。

で長時間実行される非同期タスクを実行して完全にレンダリングするコンポーネントのユーザー エクスペリエンスを向上させるには、静的サーバー側レンダリング (静的 SSR) または事前レンダリングによるOnInitializedAsyncを使用します。 詳細については、次のリソースを参照してください。

パラメーターが設定された後 (OnParametersSet{Async})

OnParametersSet または OnParametersSetAsync が呼び出されます。

  • コンポーネントが OnInitialized または OnInitializedAsync で初期化された後。

  • 親コンポーネントが再レンダリングし、次のものを提供するとき:

    • 既知またはプリミティブ不変型 (少なくとも 1 つのパラメーターが変更された場合)。
    • 複合型のパラメーター。 フレームワークは、複合型のパラメーターの値が内部で変更されているかどうかを認識できないため、1 つ以上の複合型のパラメーターが存在する場合、フレームワークではパラメーター セットが常に変更済みとして扱われます。

    レンダリング規則の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。

同期メソッドは、非同期メソッドの前に呼び出されます。

このメソッドはパラメーター値が変更されていない場合でも呼び出すことができます。 この動作は、これらのパラメータに依存するデータまたは状態を再初期化する前に、パラメーター値が確かに変更されているかどうかを確認する追加ロジックを開発者がメソッド内に実装する必要性を明確にします。

次のコンポーネントの例では、URL でコンポーネントのページに移動します。

  • StartDate によって受信された開始日がある場合: /on-parameters-set/2021-03-19
  • 開始日がなく、StartDate に現在の現地時刻の値が割り当てられている場合: /on-parameters-set

コンポーネント ルートでは、DateTime パラメーターをルート制約 datetime で制限すること、およびパラメーターを省略可能にすることを両方行うことはできません。 したがって、次の OnParamsSet コンポーネントでは、2 つの @page ディレクティブを使用して、URL に指定された日付セグメントを含む場合と含まない場合のルーティングを処理しています。

OnParamsSet.razor:

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}

パラメーターとプロパティ値を適用するときの非同期処理は、OnParametersSetAsync ライフサイクル イベント中に発生する必要があります。

protected override async Task OnParametersSetAsync()
{
    await ...
}

カスタム基底クラスをカスタム初期化ロジックで使用する場合は、基底クラスで OnParametersSetAsync を呼び出します。

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

カスタム基底クラスをカスタム ロジックで使用しない限り、ComponentBase.OnParametersSetAsync を呼び出す必要はありません。 詳細については、「基底クラスのライフサイクル メソッド」セクションを参照してください。

コンポーネントは、OnParametersSetAsync が潜在的に不完全な Task を待機しているときに、レンダリングに正しい状態にあることを確認する必要があります。 メソッドが不完全な Taskを返す場合、同期的に完了するメソッドの部分は、コンポーネントをレンダリングの有効な状態のままにする必要があります。 詳細については、ASP.NET Core Blazor 同期コンテキストASP.NET Core Razor コンポーネントの破棄 の概要に関する解説を参照してください。

開発者コード内にイベントハンドラーがある場合、破棄時にこれらを解除します。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。

破棄可能なコンポーネントが CancellationTokenを使用していない場合は、 OnParametersSetOnParametersSetAsync は、コンポーネントが破棄されているかどうかを確認する必要があります。 OnParametersSetAsyncが不完全なTaskを返す場合、コンポーネントは、完了したメソッドの部分が、レンダリングに有効な状態でコンポーネントを残すようにする必要があります。 詳細については、ASP.NET Core Blazor 同期コンテキストASP.NET Core Razor コンポーネントの破棄 の概要に関する解説を参照してください。

ルートのパラメーターと制約について詳しくは、「ASP.NET Core Blazor のルーティングとナビゲーション」をご覧ください。

一部のシナリオでパフォーマンスを向上させるために SetParametersAsync を手動で実装する例については、「 ASP.NET Core Blazor レンダリングパフォーマンスのベスト プラクティス」を参照してください。

コンポーネントのレンダリング後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync は、コンポーネントが対話形式でレンダリングされ、UI の更新が完了した後 (たとえば、ブラウザー DOM に要素が追加された後) に呼び出されます。 この時点で、要素およびコンポーネント参照が設定されます。 レンダリングされた DOM 要素を操作する JS 相互運用呼び出しなどの、レンダリングされたコンテンツを使用した追加の初期化手順を行うには、この段階を使用します。 同期メソッドは、非同期メソッドの前に呼び出されます。

これらのメソッドは、サーバー上でのプリレンダリング中または静的サーバー側レンダリング (静的 SSR) 中には呼び出されません。これらのプロセスはライブ ブラウザー DOM にアタッチされておらず、DOM が更新される前に既に完了しているためです。

OnAfterRenderAsync の場合、無限のレンダリング ループを回避するために、返された Task が完了した後にコンポーネントが自動的に再レンダリングされることはありません。

OnAfterRender および OnAfterRenderAsync は、コンポーネントのレンダリングが完了した後に呼び出されます。 この時点で、要素およびコンポーネント参照が設定されます。 レンダリングされた DOM 要素を操作する JS 相互運用呼び出しなどの、レンダリングされたコンテンツを使用した追加の初期化手順を行うには、この段階を使用します。 同期メソッドは、非同期メソッドの前に呼び出されます。

これらのメソッドは、プリレンダリング中には呼び出されません。プリレンダリングはライブ ブラウザー DOM にアタッチされておらず、DOM が更新される前に既に完了しているためです。

OnAfterRenderAsync の場合、無限のレンダリング ループを回避するために、返された Task が完了した後にコンポーネントが自動的に再レンダリングされることはありません。

firstRenderOnAfterRenderOnAfterRenderAsync パラメーター:

  • コンポーネント インスタンスを初めて表示するときに true に設定されます。
  • 初期化作業が確実に 1 回だけ実行されるように使用できます。

AfterRender.razor:

@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}

AfterRender.razor サンプルは、ページが読み込まれてボタンが選択されたときに、コンソールに次の出力を生成します。

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

OnAfterRenderAsync ライフサイクル イベント中に、レンダリング直後の非同期作業が発生する必要があります。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

カスタム基底クラスをカスタム初期化ロジックで使用する場合は、基底クラスで OnAfterRenderAsync を呼び出します。

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

カスタム基底クラスをカスタム ロジックで使用しない限り、ComponentBase.OnAfterRenderAsync を呼び出す必要はありません。 詳細については、「基底クラスのライフサイクル メソッド」セクションを参照してください。

Task から OnAfterRenderAsync を返した場合でも、フレームワークでは、そのタスクが完了しても、コンポーネントに対してさらにレンダリング サイクルがスケジュールされることはありません。 これは、無限のレンダリング ループを回避するためです。 これは、返されたTaskが完了するとレンダリング サイクルをさらにスケジュールする他のライフサイクル メソッドとは異なります。

OnAfterRender および OnAfterRenderAsync " はサーバーでのプリレンダリング プロセス中には呼び出されません"。 これらのメソッドは、プリレンダリングの後にコンポーネントが対話形式でレンダリングされるときに呼び出されます。 次の場合に、アプリによりプリレンダリングされます。

  1. コンポーネントがサーバー上で実行され、HTTP 応答でいくつかの静的 HTML マークアップが生成される。 このフェーズでは、OnAfterRenderOnAfterRenderAsync は呼び出されません。
  2. ブラウザーで Blazor スクリプト (blazor.{server|webassembly|web}.js) が起動すると、コンポーネントが対話型のレンダリング モードで再起動されます。 コンポーネントが再起動されると、アプリはプリレンダリング フェーズでなくなるため、OnAfterRenderOnAfterRenderAsyncが呼び出されます

開発者コード内にイベントハンドラーがある場合、破棄時にこれらを解除します。 詳細については、ASP.NET Core Razor コンポーネントの破棄を参照してください。

基底クラスのライフサイクル メソッド

Blazor のライフサイクル メソッドをオーバーライドする場合、ComponentBase の基底クラスのライフサイクル メソッドを呼び出す必要はありません。 ただし、コンポーネントは、オーバーライドされた基底クラスのライフサイクル メソッドを次の状況で呼び出す必要があります。

  • ComponentBase.SetParametersAsync をオーバーライドすると、基底クラス メソッドが他のライフサイクル メソッドを呼び出し、複雑な方法でレンダリングをトリガーするため、通常、await base.SetParametersAsync(parameters); が呼び出されます。 詳細については、「パラメーターが設定されるタイミング (SetParametersAsync)」セクションをご覧ください。
  • 基底クラス メソッドに、実行する必要があるロジックが含まれている場合。 多くの場合、ライブラリの基底クラスには実行するカスタム ライフサイクル ロジックがあるため、ライブラリ コンシューマーは通常、基底クラスを継承するときに基底クラスのライフサイクル メソッドを呼び出します。 アプリでライブラリの基底クラスを使用する場合、ガイダンスについては、ライブラリのドキュメントを参照してください。

次の例では、基底クラスの base.OnInitialized(); メソッドが確実に実行されるようにするために OnInitialized を呼び出します。 この呼び出しがないと、BlazorRocksBase2.OnInitialized は実行されません。

BlazorRocks2.razor:

@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}

BlazorRocksBase2.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}

状態変更 (StateHasChanged)

StateHasChanged は、状態が変更されたことをコンポーネントに通知します。 該当する場合、StateHasChanged を呼び出すと、アプリのメイン スレッドが解放されたときに発生する再レンダリングがエンキューされます。

StateHasChanged は、EventCallback メソッドに対して自動的に呼び出されます。 イベント コールバックについて詳しくは、「ASP.NET Core Blazor のイベント処理」をご覧ください。

コンポーネントのレンダリングと StateHasChanged を呼び出すタイミング (ComponentBase.InvokeAsync を使用して呼び出すタイミングなど) の詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」を参照してください。

レンダリング時に不完全な非同期アクションを処理する

ライフサイクル イベントで実行される非同期アクションは、コンポーネントがレンダリングされる前に完了していない可能性があります。 ライフサイクル メソッドの実行中に、オブジェクトが null またはデータが不完全に設定されている可能性があります。 オブジェクトが初期化されていることを確認するレンダリング ロジックを提供します。 オブジェクトが null の間、プレースホルダー UI 要素 (読み込みメッセージなど) をレンダリングします。

次の Slow コンポーネントでは、実行時間の長いタスクを非同期的に実行するために OnInitializedAsync がオーバーライドされます。 isLoadingtrueされている間、読み込みメッセージがユーザーに表示されます。 Task によって返された OnInitializedAsync が完了すると、コンポーネントは更新された状態で再レンダリングされ、"Finished!" メッセージが表示されます。

Slow.razor:

@page "/slow"

<h2>Slow Component</h2>

@if (isLoading)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>Finished!</div>
}

@code {
    private bool isLoading = true;

    protected override async Task OnInitializedAsync()
    {
        await LoadDataAsync();
        isLoading = false;
    }

    private Task LoadDataAsync()
    {
        return Task.Delay(10000);
    }
}

上記のコンポーネントは、isLoading 変数を使用して読み込みメッセージを表示します。 同様の方法が、データをコレクションに読み込み、読み込みメッセージを表示するためにコレクションが null されているかどうかを確認するコンポーネントにも使用されます。 次の例では、movies コレクションを nullでチェックし、読み込みメッセージを表示するか、ムービーのコレクションを表示します。

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    @* display movies *@
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovies();
    }
}

プリレンダリングは、休止を待機します。つまり、レンダリング ツリー内のすべてのコンポーネントがレンダリングを完了するまで、コンポーネントはレンダリングされません。 つまり、子コンポーネントの OnInitializedAsync メソッドがプリレンダリング中に実行時間の長いタスクを実行している間は、読み込みメッセージが表示されません。 この動作を示すには、上記の Slow コンポーネントをテスト アプリの Home コンポーネントに配置します。

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<Slow />

このセクションの例では、OnInitializedAsync ライフサイクル メソッドについて説明しますが、プリレンダリング中に実行される他のライフサイクル メソッドでは、コンポーネントの最終的なレンダリングが遅れる可能性があります。 OnAfterRender{Async} のみがプリレンダリング中に実行されず、休止による遅延の影響を受けられません。

プリレンダリング中、Home コンポーネントは、Slow コンポーネントがレンダリングされるまでレンダリングされません。レンダリングには 10 秒かかります。 この 10 秒間は UI が空白になり、読み込みメッセージはありません。 プリレンダリング後、Home コンポーネントがレンダリングされ、Slow コンポーネントの読み込みメッセージが表示されます。 さらに 10 秒後、Slow コンポーネントは最終的に完了したメッセージを表示します。

前のデモで示したように、プリレンダリング中の休止により、ユーザー エクスペリエンスが低下します。 ユーザー エクスペリエンスを向上させるには、まず ストリーミング レンダリング を実装して、プリレンダリング中に非同期タスクが完了するのを待機しないようにします。

[StreamRendering] 属性Slow コンポーネントに追加します (.NET 8 では [StreamRendering(true)] を使用します)。

@attribute [StreamRendering]

Home コンポーネントがプリレンダリングされている場合、Slow コンポーネントは読み込みメッセージと共にすばやくレンダリングされます。 Home コンポーネントは、Slow コンポーネントのレンダリングが完了するまで 10 秒間待機しません。 ただし、プリレンダリングの終了時に表示された完成したメッセージは読み込みメッセージに置き換えられ、コンポーネントが最終的にレンダリングされます。これはもう 10 秒の遅延です。 これは、Slow コンポーネントが 2 回レンダリングされ、LoadDataAsync も 2 回実行されるために発生します。 コンポーネントがサービスやデータベースなどのリソースにアクセスしている場合、サービス呼び出しとデータベース クエリを 2 回実行すると、アプリのリソースに望ましくない負荷が発生します。

読み込みメッセージの二重レンダリングとサービス呼び出しとデータベース呼び出しの再実行に対処するには、PersistentComponentState コンポーネントに対する次の更新で示すように、コンポーネントの最終的なレンダリングのために Slow でプリレンダリングされた状態を保持します。

@page "/slow"
@attribute [StreamRendering]

<h2>Slow Component</h2>

@if (Data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@Data</div>
}

@code {
    [SupplyParameterFromPersistentComponentState]
    public string? Data { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Data ??= await LoadDataAsync();
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }
}
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState

<h2>Slow Component</h2>

@if (data is null)
{
    <div><em>Loading...</em></div>
}
else
{
    <div>@data</div>
}

@code {
    private string? data;
    private PersistingComponentStateSubscription persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
        {
            data = await LoadDataAsync();
        }
        else
        {
            data = restored!;
        }

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

    private Task PersistData()
    {
        ApplicationState.PersistAsJson(nameof(data), data);

        return Task.CompletedTask;
    }

    private async Task<string> LoadDataAsync()
    {
        await Task.Delay(5000);
        return "Finished!";
    }

    void IDisposable.Dispose()
    {
        persistingSubscription.Dispose();
    }
}

ストリーミング レンダリングと永続的なコンポーネントの状態を組み合わせることにより、

  • サービスとデータベースでは、コンポーネントの初期化を 1 回だけ呼び出す必要があります。
  • コンポーネントは、実行時間の長いタスク中にメッセージを読み込んで UI をすばやくレンダリングし、最適なユーザー エクスペリエンスを実現します。

詳細については、次のリソースを参照してください。

:::moniker-end

プリレンダリング中に休止すると、ユーザー エクスペリエンスが低下します。 遅延は、.NET 8 以降をターゲットとするアプリで、ストリーミング レンダリングと呼ばれる機能を使用して対処できます。通常は、非同期タスクの完了を待つのを避けるために、 のプリレンダリング中にコンポーネントの状態を保持 と組み合わせることができます。 .NET 8 より前のバージョンの .NET では、最終レンダリング後にデータを読み込む 実行時間の長いバックグラウンド タスク を実行することで、休止によるレンダリングの遅延を軽減することができます。

エラーの処理

ライフサイクル メソッドの実行中のエラー処理について詳しくは、「ASP.NET Core Blazor アプリのエラーを処理する」をご覧ください。

プリレンダリング後のステートフル再接続

サーバーでのプリレンダリングの場合、コンポーネントは最初に、ページの一部として静的にレンダリングされます。 ブラウザーがサーバーへの SignalR 接続を確立すると、コンポーネントが "再度" レンダリングされ、やりとりできるようになります。 コンポーネントを初期化するための OnInitialized{Async} ライフサイクル メソッドが存在する場合、メソッドは "2 回" 実行されます。

  • コンポーネントが静的にプリレンダリングされたとき。
  • サーバー接続が確立された後。

これにより、コンポーネントが最終的にレンダリングされるときに、UI に表示されるデータが大幅に変わる可能性があります。 この動作を回避するには、プリレンダリング中に状態をキャッシュする識別子を渡し、プリレンダリング後に状態を取得します。

以下のコードは、プリレンダリングによるデータ表示の変更を回避する WeatherForecastService を示しています。 待機中の Delay (await Task.Delay(...)) は、GetForecastAsync メソッドからデータを返す前に短い遅延をシミュレートします。

アプリの IMemoryCache ファイルで、サービス コレクションに対して AddMemoryCache を使用して Program サービスを追加します。

builder.Services.AddMemoryCache();

WeatherForecastService.cs:

using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)
{
    private static readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    public IMemoryCache MemoryCache { get; } = memoryCache;

    public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)
{
    private static readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    public IMemoryCache MemoryCache { get; } = memoryCache;

    public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}

RenderMode について詳しくは、「ASP.NET Core BlazorSignalR ガイダンス」をご覧ください。

このセクションの内容は、Blazor Web App とステートフル SignalR の再接続に焦点を当てています。 プリレンダリング中に初期化コードの実行中の状態を保持するには、「ASP.NET Core Razor コンポーネントのプリレンダリングを行う」をご覧ください。

このセクションのコンテンツでは、Blazor Server およびステートフルな SignalR の "再接続" に焦点を当てていますが、ホストされた ソリューション (Blazor WebAssembly) でのプリレンダリングのシナリオでは、開発者コードを 2 回実行しないようにするための同様の条件とアプローチが必要です。WebAssemblyPrerendered 初期化コードの実行中にプリレンダリングしながら状態を保持するには、Razor方法を参照してください。

JavaScript の相互運用による事前レンダリング

このセクションは、Razor コンポーネントをプリレンダリングするサーバー側アプリにあてはまります。 プリレンダリングについては、「ASP.NET Core Razor コンポーネントのプリレンダリングを行う」内で説明しています。

の Blazor Web App向け内部ナビゲーションは、サーバーからの新しいページ コンテンツを要求しません。 そのため、内部ページ要求ではプリレンダリングは行われません。 アプリで対話型ルーティングを採用している場合は、プリレンダリング動作を示すコンポーネントの例でページ全体の再読み込みを実行します。 詳細については、「ASP.NET Core Razor のプリレンダリングとコンポーネント」をご覧ください。

このセクションは、Blazor WebAssembly コンポーネントをプリレンダリングするサーバー側アプリおよびホストされている Razor アプリに適用されます。 ASP.NET Core コンポーネントのプリレンダリングについては、MVC または Razor PagesRazorと統合する方法を説明しています。

プリレンダリング中に JavaScript (JS) を呼び出すことはできません。 次の例では、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JS 相互運用を使用する方法を示します。

次の scrollElementIntoView 関数:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

IJSRuntime.InvokeAsync でコンポーネント コードの JS 関数を呼び出すと、以前のライフサイクル メソッドではなく、ElementReferenceOnAfterRenderAsync でのみ使用されます。コンポーネントがレンダリングされるまで HTML DOM 要素が存在しないためです。

StateHasChanged (参照元) は、JS の相互運用呼び出しから取得された新しい状態でコンポーネントの再レンダリングをエンキューするために呼び出されます (詳細については、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください)。 StateHasChangedscrollPositionnull である場合にのみ呼び出されるため、無限ループが作成されることはありません。

PrerenderedInterop.razor:

@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

前の例では、クライアントがグローバル関数で汚染されます。 実稼働アプリでのより適切なアプローチについては、「JavaScript モジュール内の JavaScript 分離」を参照してください。

取り消し可能なバックグラウンド作業

ネットワーク呼び出し (HttpClient) の実行やデータベースとの対話など、コンポーネントによって実行時間の長いバックグラウンド作業が実行されることがよくあります。 いくつかの状況でシステム リソースを節約するために、バックグラウンド作業を停止することをお勧めします。 たとえば、ユーザーがコンポーネントの操作を止めても、バックグラウンドの非同期操作は自動的に停止しません。

バックグラウンド作業項目の取り消しが必要になるその他の理由には、次のようなものがあります。

  • 実行中のバックグラウンド タスクは、不完全な入力データまたは処理パラメーターを使用して開始されました。
  • 現在実行中のバックグラウンド作業項目のセットを、新しい作業項目のセットに置き換える必要があります。
  • 現在実行中のタスクの優先度を変更する必要があります。
  • サーバーを再展開するには、アプリをシャットダウンする必要があります。
  • サーバー リソースが制限され、バックグラウンド作業項目の再スケジュールが必要になりました。

コンポーネントに取り消し可能なバックグラウンド作業パターンを実装するには:

次に例を示します。

  • await Task.Delay(10000, cts.Token); は、実行時間が長い非同期のバックグラウンド作業を表します。
  • BackgroundResourceMethod は、メソッドが呼び出される前に Resource が破棄された場合に開始されない、実行時間が長いバックグラウンド メソッドを表します。

BackgroundWork.razor:

@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new Resource();
    private CancellationTokenSource cts = new CancellationTokenSource();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}

バックグラウンド作業の実行中に読み込みインジケーターを表示するには、次の方法を使用します。

Loadingに子コンテンツを表示できるRenderFragment パラメーターを使用して、読み込みインジケーター コンポーネントを作成します。 Loading パラメーターの場合:

  • true時には、読み込みインジケーターを表示します。
  • falseするときは、コンポーネントのコンテンツ (ChildContent) をレンダリングします。 詳細については、「 子コンテンツのレンダリング フラグメント」を参照してください。

ContentLoading.razor:

@if (Loading)
{
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    [Parameter]
    public bool Loading { get; set; }
}

インジケーターの CSS スタイルを追加するには、<head> コンテンツに HeadContent コンポーネントを使用してスタイルを追加します。 詳細については、「 ASP.NET Core Blazor アプリのヘッド コンテンツを制御する」を参照してください。

@if (Loading)
{
    <!-- OPTIONAL ...
    <HeadContent>
        <style>
            ...
        </style>
    </HeadContent>
    -->
    <progress id="loadingIndicator" aria-label="Content loading…"></progress>
}
else
{
    @ChildContent
}

...

コンポーネントの Razor マークアップを ContentLoading コンポーネントでラップし、コンポーネントによって初期化作業が実行されるときに、C# フィールドの値を Loading パラメーターに渡します。

<ContentLoading Loading="@loading">
    ...
</ContentLoading>

@code {
    private bool loading = true;
    ...

    protected override async Task OnInitializedAsync()
    {
        await LongRunningWork().ContinueWith(_ => loading = false);
    }

    ...
}

Blazor Server 再接続イベント

この記事で説明するコンポーネント ライフサイクル イベントは、サーバー側の再接続イベント ハンドラーとは別個に動作します。 クライアントへの SignalR 接続が失われた場合は、UI の更新だけが中断されます。 その接続が再確立されると、UI の更新が再開されます。 回線ハンドラーのイベントと構成について詳しくは、「ASP.NET Core BlazorSignalR ガイダンス」をご覧ください。

その他のリソース

Razor コンポーネントのライフサイクル外でキャッチされた例外を処理する