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

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

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

ライフサイクル イベント

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

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

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

注意

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

Component lifecycle events of a Razor component in Blazor

ドキュメント オブジェクト モデル (DOM) イベント処理:

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

Document Object Model (DOM) event processing

Render のライフサイクル:

  1. コンポーネントでそれ以上のレンダリング操作を行わないようにします。
    • 最初のレンダリングの後。
    • ShouldRenderfalse の場合。
  2. レンダリング ツリーの差分を作成し、コンポーネントをレンダリングします。
  3. DOM が更新されるのを待機します。
  4. OnAfterRender{Async} を呼び出します。

Render lifecycle

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

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

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

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

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

base.SetParametersAsync が呼び出されない場合、開発者コードでは、必要に応じて受信パラメーター値を解釈できます。 たとえば、受信したパラメーターをクラスのプロパティに割り当てる必要はありません。

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

Pages/SetParamsAsync.razor:

@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})

OnInitialized および OnInitializedAsync は、コンポーネントが SetParametersAsync で初期パラメーターを受け取った後で初期化されるときに呼び出されます。

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

Pages/OnInit.razor:

@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 でコンテンツを "2 回" プリレンダリングする Blazor アプリ。

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

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

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

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

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

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

Pages/OnParamsSet.razor:

注意

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

@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 ...
}

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

OnAfterRenderOnAfterRenderAsyncfirstRender パラメーター:

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

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

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

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

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

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

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

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

状態変更 (StateHasChanged)

StateHasChanged は、状態が変更されたことをコンポーネントに通知します。 必要に応じて、StateHasChanged を呼び出すと、コンポーネントが再レンダリングされます。

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

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

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

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

Blazor テンプレートの FetchData コンポーネントでは、予測データ (forecasts) を非同期に受信するように、OnInitializedAsync がオーバーライドされます。 forecastsnull の場合、読み込みメッセージがユーザーに表示されます。 OnInitializedAsync によって返された Task が完了すると、コンポーネントは更新された状態で再レンダリングされます。

Blazor Server テンプレートの Pages/FetchData.razor は以下のようになります。

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

エラーの処理

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

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

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

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

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

次のコードは、二重レンダリングを回避するテンプレートベースの Blazor Server アプリ内で更新される WeatherForecastService を示しています。 次の例では、待機中の Delay (await Task.Delay(...)) は、GetForecastAsync メソッドからデータを返す前に短い遅延をシミュレートします。

WeatherForecastService.cs:

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)
            });

            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 Server およびステートフルな SignalR の "再接続" に焦点を当てていますが、ホストされた Blazor WebAssembly アプリ (WebAssemblyPrerendered) でのプリレンダリングのシナリオでは、開発者コードを 2 回実行しないようにするための同様の条件とアプローチが必要です。 プリレンダリング中に初期化コードの実行中の状態を保持するには、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」をご覧ください。

アプリがプリレンダリングされていることを検出する

このセクションは Razor コンポーネントをプリレンダリングする Blazor Server およびホストされている Blazor WebAssembly アプリに適用されます。 プリレンダリングについては、「ASP.NET Core コンポーネントのプリレンダリングと統合を行う」で説明しています。

アプリでプリレンダリングするとき、JavaScript の呼び出しなどの特定のアクションは不可能です。 コンポーネントは、プリレンダリング時に異なるレンダリングが必要になる場合があります。

次の例の場合、setElementText1 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は JSRuntimeExtensions.InvokeVoidAsync を指定して呼び出され、値を返しません。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

このような呼び出しの動作が保証されるまで JavaScript 相互運用呼び出しを遅延させるには、 ライフサイクル イベントをオーバーライドします。 このイベントは、アプリが完全にレンダリングされた後にのみ呼び出されます。

Pages/PrerenderedInterop1.razor:

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

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

注意

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

例:

export setElementText1 = (element, text) => element.innerText = text;

次のコンポーネントは、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JavaScript の相互運用を使用する方法を示しています。 コンポーネントには、OnAfterRenderAsync 内からレンダリングの更新をトリガーできることが示されています。 開発者はこのシナリオで無限ループを作成しないように注意する必要があります。

次の例の場合、setElementText2 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は IJSRuntime.InvokeAsync を指定して呼び出され、次の値を返します。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

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

StateHasChanged は、JavaScript の相互運用呼び出しから取得された新しい状態でコンポーネントを再度レンダリングするために呼び出されます (詳しくは、「StateHasChanged」をご覧ください)。 StateHasChangeddatanull である場合にのみ呼び出されるため、このコードで無限ループが作成されることはありません。

Pages/PrerenderedInterop2.razor:

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

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

注意

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

例:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

IDisposableIAsyncDisposable を使用したコンポーネントの破棄

コンポーネントにより IDisposableIAsyncDisposable または 両方が実装される場合、コンポーネントが UI から削除されるとフレームワークによりアンマネージド リソースの廃棄が呼び出されます。 破棄は、コンポーネントの初期化中など、いつでも実行できます。

コンポーネントでは、IDisposableIAsyncDisposable の同時実装を必須にすべきではありません。 両方が実装される場合、フレームワークは非同期オーバーロードのみを実行します。

開発者コードでは、IAsyncDisposable 実装の完了に時間がかからないようにする必要があります。

同期的 IDisposable

同期的な廃棄タスクの場合は、IDisposable.Dispose を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IDisposable を実装 します。
  • IDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

1 つのオブジェクトの破棄が必要な場合、Dispose が呼び出されるきに、ラムダを使用してオブジェクトを破棄できます。 次の例は記事「ASP.NET Core Razor コンポーネントのレンダリング」のもので、Timer の破棄のためのラムダ式の使用を示しています。

Pages/CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

注意

前の例では、StateHasChanged への呼び出しが ComponentBase.InvokeAsync への呼び出しによってラップされています。そのコールバックが Blazor の同期コンテキスト外で呼び出されているためです。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。

オブジェクトが OnInitialized/OnInitializedAsync などのライフサイクル メソッドで作成されている場合は、Dispose を呼び出す前に null を確認します。

Pages/CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

詳細については、次を参照してください。

非同期的 IAsyncDisposable

非同期的な廃棄タスクの場合は、IAsyncDisposable.DisposeAsync を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IAsyncDisposable を実装 します。
  • IAsyncDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

詳細については、次を参照してください。

破棄されたオブジェクトへの null の割り当て

通常、Dispose/DisposeAsync を呼び出した後、破棄されたオブジェクトに null を割り当てる必要はありません。 null の割り当てに関するまれなケースは次のとおりです。

  • オブジェクトの型が適切に実装されず、Dispose/DisposeAsync への繰り返し呼び出しが許容されない場合は、Dispose/DisposeAsync のそれ以降の呼び出しを適切にスキップするために、廃棄後に null を割り当てます。
  • 長期プロセスが破棄されたオブジェクトへの参照を保持し続ける場合、null を割り当てると、そのオブジェクトへの参照を保持している長期プロセスがあっても、ガベージ コレクターによってオブジェクトを解放できます。

これらは通常ではないシナリオです。 正しく実装され、正常に動作するオブジェクトの場合、破棄されたオブジェクトに null を割り当てる意味はありません。 オブジェクトに null を割り当てる必要があるまれなケースでは、理由を記録し、null を割り当てる必要性を防ぐソリューションを探すことをお勧めします。

StateHasChanged

Note

Dispose では、StateHasChanged の呼び出しはサポートされていません。 StateHasChanged は、レンダラーの破棄の一部として呼び出されることがあるため、その時点での UI 更新の要求はサポートされていません。

イベント ハンドラー

常に .NET イベントからイベント ハンドラーのサブスクライブを解除します。 次の Blazor フォームの例は、Dispose メソッドでイベント ハンドラーの登録を解除する方法を示しています。

  • プライベート フィールドとラムダのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        private EventHandler<FieldChangedEventArgs>? fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • プライベート メソッドのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

匿名関数、メソッド、式

匿名関数、メソッド、または式が使用されている場合、IDisposable の実装やデリゲートの登録解除を行う必要はありません。 しかし、デリゲートの登録解除に失敗することは、イベントを公開するオブジェクトが、デリゲートを登録するコンポーネントの有効期間を超えている場合に問題になります。 この場合、登録されたデリゲートによって元のオブジェクトが保持されているため、メモリ リークが発生します。 そのため、イベント デリゲートがすぐに破棄されることがわかっている場合にのみ、次のアプローチを使用してください。 破棄が必要なオブジェクトの有効期間が不明な場合は、前の例で示したように、デリゲート メソッドを登録し、そのデリゲートを適切に破棄します。

  • 匿名のラムダ メソッドのアプローチ (明示的な破棄は不要):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • 匿名のラムダ式のアプローチ (明示的な破棄は不要):

    private ValidationMessageStore? messageStore;
    
    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    匿名ラムダ式を使用した上記のコードの完全な例は、記事「ASP.NET Core Blazor のフォームと検証」にあります。

詳細については、「アンマネージ リソースのクリーンアップ」と、その後に続く Dispose および DisposeAsync メソッドの実装に関するトピックを参照してください。

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

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

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

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

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

次に例を示します。

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

Pages/BackgroundWork.razor:

@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;
        }
    }
}

Blazor Server 再接続イベント

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

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

ライフサイクル イベント

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

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

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

注意

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

Component lifecycle events of a Razor component in Blazor

ドキュメント オブジェクト モデル (DOM) イベント処理:

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

Document Object Model (DOM) event processing

Render のライフサイクル:

  1. コンポーネントでそれ以上のレンダリング操作を行わないようにします。
    • 最初のレンダリングの後。
    • ShouldRenderfalse の場合。
  2. レンダリング ツリーの差分を作成し、コンポーネントをレンダリングします。
  3. DOM が更新されるのを待機します。
  4. OnAfterRender{Async} を呼び出します。

Render lifecycle

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

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

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

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

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

base.SetParametersAsync が呼び出されない場合、開発者コードでは、必要に応じて受信パラメーター値を解釈できます。 たとえば、受信したパラメーターをクラスのプロパティに割り当てる必要はありません。

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

Pages/SetParamsAsync.razor:

@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})

OnInitialized および OnInitializedAsync は、コンポーネントが SetParametersAsync で初期パラメーターを受け取った後で初期化されるときに呼び出されます。

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

Pages/OnInit.razor:

@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 でコンテンツを "2 回" プリレンダリングする Blazor アプリ。

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

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

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

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

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

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

Pages/OnParamsSet.razor:

注意

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

@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 ...
}

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

OnAfterRenderOnAfterRenderAsyncfirstRender パラメーター:

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

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

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

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

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

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

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

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

状態変更 (StateHasChanged)

StateHasChanged は、状態が変更されたことをコンポーネントに通知します。 必要に応じて、StateHasChanged を呼び出すと、コンポーネントが再レンダリングされます。

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

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

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

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

Blazor テンプレートの FetchData コンポーネントでは、予測データ (forecasts) を非同期に受信するように、OnInitializedAsync がオーバーライドされます。 forecastsnull の場合、読み込みメッセージがユーザーに表示されます。 OnInitializedAsync によって返された Task が完了すると、コンポーネントは更新された状態で再レンダリングされます。

Blazor Server テンプレートの Pages/FetchData.razor は以下のようになります。

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

エラーの処理

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

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

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

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

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

次のコードは、二重レンダリングを回避するテンプレートベースの Blazor Server アプリ内で更新される WeatherForecastService を示しています。 次の例では、待機中の Delay (await Task.Delay(...)) は、GetForecastAsync メソッドからデータを返す前に短い遅延をシミュレートします。

WeatherForecastService.cs:

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 Server およびステートフルな SignalR の "再接続" に焦点を当てていますが、ホストされた Blazor WebAssembly アプリ (WebAssemblyPrerendered) でのプリレンダリングのシナリオでは、開発者コードを 2 回実行しないようにするための同様の条件とアプローチが必要です。 プリレンダリング中に初期化コードの実行中の状態を保持するには、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」をご覧ください。

アプリがプリレンダリングされていることを検出する

このセクションは Razor コンポーネントをプリレンダリングする Blazor Server およびホストされている Blazor WebAssembly アプリに適用されます。 プリレンダリングについては、「ASP.NET Core コンポーネントのプリレンダリングと統合を行う」で説明しています。

アプリでプリレンダリングするとき、JavaScript の呼び出しなどの特定のアクションは不可能です。 コンポーネントは、プリレンダリング時に異なるレンダリングが必要になる場合があります。

次の例の場合、setElementText1 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は JSRuntimeExtensions.InvokeVoidAsync を指定して呼び出され、値を返しません。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

このような呼び出しの動作が保証されるまで JavaScript 相互運用呼び出しを遅延させるには、 ライフサイクル イベントをオーバーライドします。 このイベントは、アプリが完全にレンダリングされた後にのみ呼び出されます。

Pages/PrerenderedInterop1.razor:

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

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

注意

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

例:

export setElementText1 = (element, text) => element.innerText = text;

次のコンポーネントは、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JavaScript の相互運用を使用する方法を示しています。 コンポーネントには、OnAfterRenderAsync 内からレンダリングの更新をトリガーできることが示されています。 開発者はこのシナリオで無限ループを作成しないように注意する必要があります。

次の例の場合、setElementText2 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は IJSRuntime.InvokeAsync を指定して呼び出され、次の値を返します。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

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

StateHasChanged は、JavaScript の相互運用呼び出しから取得された新しい状態でコンポーネントを再度レンダリングするために呼び出されます (詳しくは、「StateHasChanged」をご覧ください)。 StateHasChangeddatanull である場合にのみ呼び出されるため、このコードで無限ループが作成されることはありません。

Pages/PrerenderedInterop2.razor:

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

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

注意

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

例:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

IDisposableIAsyncDisposable を使用したコンポーネントの破棄

コンポーネントにより IDisposableIAsyncDisposable または 両方が実装される場合、コンポーネントが UI から削除されるとフレームワークによりアンマネージド リソースの廃棄が呼び出されます。 破棄は、コンポーネントの初期化中など、いつでも実行できます。

コンポーネントでは、IDisposableIAsyncDisposable の同時実装を必須にすべきではありません。 両方が実装される場合、フレームワークは非同期オーバーロードのみを実行します。

開発者コードでは、IAsyncDisposable 実装の完了に時間がかからないようにする必要があります。

同期的 IDisposable

同期的な廃棄タスクの場合は、IDisposable.Dispose を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IDisposable を実装 します。
  • IDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

1 つのオブジェクトの破棄が必要な場合、Dispose が呼び出されるきに、ラムダを使用してオブジェクトを破棄できます。 次の例は記事「ASP.NET Core Razor コンポーネントのレンダリング」のもので、Timer の破棄のためのラムダ式の使用を示しています。

Pages/CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

注意

前の例では、StateHasChanged への呼び出しが ComponentBase.InvokeAsync への呼び出しによってラップされています。そのコールバックが Blazor の同期コンテキスト外で呼び出されているためです。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。

オブジェクトが OnInitialized/OnInitializedAsync などのライフサイクル メソッドで作成されている場合は、Dispose を呼び出す前に null を確認します。

Pages/CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

詳細については、次を参照してください。

非同期的 IAsyncDisposable

非同期的な廃棄タスクの場合は、IAsyncDisposable.DisposeAsync を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IAsyncDisposable を実装 します。
  • IAsyncDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

詳細については、次を参照してください。

破棄されたオブジェクトへの null の割り当て

通常、Dispose/DisposeAsync を呼び出した後、破棄されたオブジェクトに null を割り当てる必要はありません。 null の割り当てに関するまれなケースは次のとおりです。

  • オブジェクトの型が適切に実装されず、Dispose/DisposeAsync への繰り返し呼び出しが許容されない場合は、Dispose/DisposeAsync のそれ以降の呼び出しを適切にスキップするために、廃棄後に null を割り当てます。
  • 長期プロセスが破棄されたオブジェクトへの参照を保持し続ける場合、null を割り当てると、そのオブジェクトへの参照を保持している長期プロセスがあっても、ガベージ コレクターによってオブジェクトを解放できます。

これらは通常ではないシナリオです。 正しく実装され、正常に動作するオブジェクトの場合、破棄されたオブジェクトに null を割り当てる意味はありません。 オブジェクトに null を割り当てる必要があるまれなケースでは、理由を記録し、null を割り当てる必要性を防ぐソリューションを探すことをお勧めします。

StateHasChanged

Note

Dispose では、StateHasChanged の呼び出しはサポートされていません。 StateHasChanged は、レンダラーの破棄の一部として呼び出されることがあるため、その時点での UI 更新の要求はサポートされていません。

イベント ハンドラー

常に .NET イベントからイベント ハンドラーのサブスクライブを解除します。 次の Blazor フォームの例は、Dispose メソッドでイベント ハンドラーの登録を解除する方法を示しています。

  • プライベート フィールドとラムダのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        // ...
        private EventHandler<FieldChangedEventArgs> fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                // ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • プライベート メソッドのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        // ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            // ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

匿名関数、メソッド、式

匿名関数、メソッド、または式が使用されている場合、IDisposable の実装やデリゲートの登録解除を行う必要はありません。 しかし、デリゲートの登録解除に失敗することは、イベントを公開するオブジェクトが、デリゲートを登録するコンポーネントの有効期間を超えている場合に問題になります。 この場合、登録されたデリゲートによって元のオブジェクトが保持されているため、メモリ リークが発生します。 そのため、イベント デリゲートがすぐに破棄されることがわかっている場合にのみ、次のアプローチを使用してください。 破棄が必要なオブジェクトの有効期間が不明な場合は、前の例で示したように、デリゲート メソッドを登録し、そのデリゲートを適切に破棄します。

  • 匿名のラムダ メソッドのアプローチ (明示的な破棄は不要):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • 匿名のラムダ式のアプローチ (明示的な破棄は不要):

    private ValidationMessageStore messageStore;
    
    [CascadingParameter]
    private EditContext CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    匿名ラムダ式を使用した上記のコードの完全な例は、記事「ASP.NET Core Blazor のフォームと検証」にあります。

詳細については、「アンマネージ リソースのクリーンアップ」と、その後に続く Dispose および DisposeAsync メソッドの実装に関するトピックを参照してください。

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

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

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

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

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

次に例を示します。

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

Pages/BackgroundWork.razor:

@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;
        }
    }
}

Blazor Server 再接続イベント

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

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

ライフサイクル イベント

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

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

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

注意

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

Component lifecycle events of a Razor component in Blazor

ドキュメント オブジェクト モデル (DOM) イベント処理:

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

Document Object Model (DOM) event processing

Render のライフサイクル:

  1. コンポーネントでそれ以上のレンダリング操作を行わないようにします。
    • 最初のレンダリングの後。
    • ShouldRenderfalse の場合。
  2. レンダリング ツリーの差分を作成し、コンポーネントをレンダリングします。
  3. DOM が更新されるのを待機します。
  4. OnAfterRender{Async} を呼び出します。

Render lifecycle

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

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

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

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

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

base.SetParametersAsync が呼び出されない場合、開発者コードでは、必要に応じて受信パラメーター値を解釈できます。 たとえば、受信したパラメーターをクラスのプロパティに割り当てる必要はありません。

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

Pages/SetParamsAsync.razor:

@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})

OnInitialized および OnInitializedAsync は、コンポーネントが SetParametersAsync で初期パラメーターを受け取った後で初期化されるときに呼び出されます。

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

Pages/OnInit.razor:

@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 でコンテンツを "2 回" プリレンダリングする Blazor アプリ。

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

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

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

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

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

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

Pages/OnParamsSet.razor:

@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 ...
}

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

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

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

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

OnAfterRenderOnAfterRenderAsyncfirstRender パラメーター:

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

Pages/AfterRender.razor:

@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

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

@code {
    private string message = "Initial assigned message.";

    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender(1): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);

        if (firstRender)
        {
            message = "Executed for the first render.";
        }
        else
        {
            message = "Executed after the first render.";
        }

        Logger.LogInformation("OnAfterRender(2): firstRender: " +
            "{FirstRender}, message: {Message}", firstRender, message);
    }

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

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

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

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

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

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

開発者コード内にイベント ハンドラーが提供されている場合、破棄時にこれらをアンフックします。 詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

状態変更 (StateHasChanged)

StateHasChanged は、状態が変更されたことをコンポーネントに通知します。 必要に応じて、StateHasChanged を呼び出すと、コンポーネントが再レンダリングされます。

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

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

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

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

Blazor テンプレートの FetchData コンポーネントでは、予測データ (forecasts) を非同期に受信するように、OnInitializedAsync がオーバーライドされます。 forecastsnull の場合、読み込みメッセージがユーザーに表示されます。 OnInitializedAsync によって返された Task が完了すると、コンポーネントは更新された状態で再レンダリングされます。

Blazor Server テンプレートの Pages/FetchData.razor は以下のようになります。

@page "/fetchdata"
@using BlazorSample.Data
@inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from a service.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <!-- forecast data in table element content -->
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

エラーの処理

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

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

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

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

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

次のコードは、二重レンダリングを回避するテンプレートベースの Blazor Server アプリ内で更新される WeatherForecastService を示しています。 次の例では、待機中の Delay (await Task.Delay(...)) は、GetForecastAsync メソッドからデータを返す前に短い遅延をシミュレートします。

WeatherForecastService.cs:

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 Server およびステートフルな SignalR の "再接続" に焦点を当てていますが、ホストされた Blazor WebAssembly アプリ (WebAssemblyPrerendered) でのプリレンダリングのシナリオでは、開発者コードを 2 回実行しないようにするための同様の条件とアプローチが必要です。 プリレンダリング中に初期化コードの実行中の状態を保持するには、「ASP.NET Core Razor コンポーネントのプリレンダリングと統合を行う」をご覧ください。

アプリがプリレンダリングされていることを検出する

このセクションは Razor コンポーネントをプリレンダリングする Blazor Server およびホストされている Blazor WebAssembly アプリに適用されます。 プリレンダリングについては、「ASP.NET Core コンポーネントのプリレンダリングと統合を行う」で説明しています。

アプリでプリレンダリングするとき、JavaScript の呼び出しなどの特定のアクションは不可能です。 コンポーネントは、プリレンダリング時に異なるレンダリングが必要になる場合があります。

次の例の場合、setElementText1 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は JSRuntimeExtensions.InvokeVoidAsync を指定して呼び出され、値を返しません。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText1 = (element, text) => element.innerText = text;
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

このような呼び出しの動作が保証されるまで JavaScript 相互運用呼び出しを遅延させるには、 ライフサイクル イベントをオーバーライドします。 このイベントは、アプリが完全にレンダリングされた後にのみ呼び出されます。

Pages/PrerenderedInterop1.razor:

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

<div @ref="divElement">Text during render</div>

@code {
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync(
                "setElementText1", divElement, "Text after render");
        }
    }
}

注意

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

例:

export setElementText1 = (element, text) => element.innerText = text;

次のコンポーネントは、プリレンダリングと互換性のある方法で、コンポーネントの初期化ロジックの一部として JavaScript の相互運用を使用する方法を示しています。 コンポーネントには、OnAfterRenderAsync 内からレンダリングの更新をトリガーできることが示されています。 開発者はこのシナリオで無限ループを作成しないように注意する必要があります。

次の例の場合、setElementText2 関数は wwwroot/index.html (Blazor WebAssembly) または Pages/_Layout.cshtml (Blazor Server) の <head> 要素内に配置されます。 関数は IJSRuntime.InvokeAsync を指定して呼び出され、次の値を返します。

注意

JS の場所と実稼働アプリの推奨事項に関する一般的なガイダンスについては、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」を参照してください。

<script>
  window.setElementText2 = (element, text) => {
    element.innerText = text;
    return text;
  };
</script>

警告

前の例では、デモンストレーションのみを目的として、ドキュメント オブジェクト モデル (DOM) を直接変更しています。 JavaScript での DOM の直接変更は、ほとんどのシナリオでは推奨されません。JavaScript が Blazor の変更追跡に影響する可能性があるためです。 詳しくは、「ASP.NET Core JavaScript の相互運用性 (JS 相互運用)」をご覧ください。

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

StateHasChanged は、JavaScript の相互運用呼び出しから取得された新しい状態でコンポーネントを再度レンダリングするために呼び出されます (詳しくは、「StateHasChanged」をご覧ください)。 StateHasChangeddatanull である場合にのみ呼び出されるため、このコードで無限ループが作成されることはありません。

Pages/PrerenderedInterop2.razor:

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

<p>
    Get value via JS interop call:
    <strong id="val-get-by-interop">@(data ?? "No value yet")</strong>
</p>

<p>
    Set value via JS interop call:
</p>

<div id="val-set-by-interop" @ref="divElement"></div>

@code {
    private string? data;
    private ElementReference divElement;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && data == null)
        {
            data = await JS.InvokeAsync<string>(
                "setElementText2", divElement, "Hello from interop call!");

            StateHasChanged();
        }
    }
}

注意

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

例:

export setElementText2 = (element, text) => {
  element.innerText = text;
  return text;
};

IDisposableIAsyncDisposable を使用したコンポーネントの破棄

コンポーネントにより IDisposableIAsyncDisposable または 両方が実装される場合、コンポーネントが UI から削除されるとフレームワークによりアンマネージド リソースの廃棄が呼び出されます。 破棄は、コンポーネントの初期化中など、いつでも実行できます。

コンポーネントでは、IDisposableIAsyncDisposable の同時実装を必須にすべきではありません。 両方が実装される場合、フレームワークは非同期オーバーロードのみを実行します。

開発者コードでは、IAsyncDisposable 実装の完了に時間がかからないようにする必要があります。

同期的 IDisposable

同期的な廃棄タスクの場合は、IDisposable.Dispose を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IDisposable を実装 します。
  • IDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

1 つのオブジェクトの破棄が必要な場合、Dispose が呼び出されるきに、ラムダを使用してオブジェクトを破棄できます。 次の例は記事「ASP.NET Core Razor コンポーネントのレンダリング」のもので、Timer の破棄のためのラムダ式の使用を示しています。

Pages/CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

注意

前の例では、StateHasChanged への呼び出しが ComponentBase.InvokeAsync への呼び出しによってラップされています。そのコールバックが Blazor の同期コンテキスト外で呼び出されているためです。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。

オブジェクトが OnInitialized/OnInitializedAsync などのライフサイクル メソッドで作成されている場合は、Dispose を呼び出す前に null を確認します。

Pages/CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

詳細については、次を参照してください。

非同期的 IAsyncDisposable

非同期的な廃棄タスクの場合は、IAsyncDisposable.DisposeAsync を使用します。

次のコンポーネント:

  • @implementsRazor ディレクティブを使用して IAsyncDisposable を実装 します。
  • IAsyncDisposable を実装するアンマネージド型である obj を破棄します。
  • obj がライフサイクル メソッド (表示されていない) で作成されたため、null チェックが実行されます。
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

詳細については、次を参照してください。

破棄されたオブジェクトへの null の割り当て

通常、Dispose/DisposeAsync を呼び出した後、破棄されたオブジェクトに null を割り当てる必要はありません。 null の割り当てに関するまれなケースは次のとおりです。

  • オブジェクトの型が適切に実装されず、Dispose/DisposeAsync への繰り返し呼び出しが許容されない場合は、Dispose/DisposeAsync のそれ以降の呼び出しを適切にスキップするために、廃棄後に null を割り当てます。
  • 長期プロセスが破棄されたオブジェクトへの参照を保持し続ける場合、null を割り当てると、そのオブジェクトへの参照を保持している長期プロセスがあっても、ガベージ コレクターによってオブジェクトを解放できます。

これらは通常ではないシナリオです。 正しく実装され、正常に動作するオブジェクトの場合、破棄されたオブジェクトに null を割り当てる意味はありません。 オブジェクトに null を割り当てる必要があるまれなケースでは、理由を記録し、null を割り当てる必要性を防ぐソリューションを探すことをお勧めします。

StateHasChanged

Note

Dispose では、StateHasChanged の呼び出しはサポートされていません。 StateHasChanged は、レンダラーの破棄の一部として呼び出されることがあるため、その時点での UI 更新の要求はサポートされていません。

イベント ハンドラー

常に .NET イベントからイベント ハンドラーのサブスクライブを解除します。 次の Blazor フォームの例は、Dispose メソッドでイベント ハンドラーの登録を解除する方法を示しています。

  • プライベート フィールドとラムダのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        // ...
        private EventHandler<FieldChangedEventArgs> fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new EditContext(model);
    
            fieldChanged = (_, __) =>
            {
                // ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • プライベート メソッドのアプローチ

    @implements IDisposable
    
    <EditForm EditContext="@editContext">
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        // ...
    
        protected override void OnInitialized()
        {
            editContext = new EditContext(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            // ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

詳細については、「IDisposableIAsyncDisposable を使用したコンポーネントの破棄」セクションを参照してください。

匿名関数、メソッド、式

匿名関数、メソッド、または式が使用されている場合、IDisposable の実装やデリゲートの登録解除を行う必要はありません。 しかし、デリゲートの登録解除に失敗することは、イベントを公開するオブジェクトが、デリゲートを登録するコンポーネントの有効期間を超えている場合に問題になります。 この場合、登録されたデリゲートによって元のオブジェクトが保持されているため、メモリ リークが発生します。 そのため、イベント デリゲートがすぐに破棄されることがわかっている場合にのみ、次のアプローチを使用してください。 破棄が必要なオブジェクトの有効期間が不明な場合は、前の例で示したように、デリゲート メソッドを登録し、そのデリゲートを適切に破棄します。

  • 匿名のラムダ メソッドのアプローチ (明示的な破棄は不要):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new EditContext(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • 匿名のラムダ式のアプローチ (明示的な破棄は不要):

    private ValidationMessageStore messageStore;
    
    [CascadingParameter]
    private EditContext CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new ValidationMessageStore(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    匿名ラムダ式を使用した上記のコードの完全な例は、記事「ASP.NET Core Blazor のフォームと検証」にあります。

詳細については、「アンマネージ リソースのクリーンアップ」と、その後に続く Dispose および DisposeAsync メソッドの実装に関するトピックを参照してください。

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

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

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

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

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

次に例を示します。

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

Pages/BackgroundWork.razor:

@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;
        }
    }
}

Blazor Server 再接続イベント

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