ASP.NET Core Razor 元件生命週期

本文說明 ASP.NET Core Razor 元件生命週期,以及如何使用生命週期事件。

元件會在 Razor 一組同步和非同步生命週期方法中處理 Razor 元件生命週期事件。 您可以在元件初始化和轉譯期間覆寫生命週期方法,以在元件中執行其他作業。

週期事件

下圖說明 Razor 元件生命週期事件。 與生命週期事件相關聯的 C# 方法會以本文下列各節中的範例定義。

元件生命週期事件:

  1. 如果元件第一次在要求上轉譯:
  2. 呼叫 OnParametersSet{Async}。 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 呈現所有同步工作並完成 Task

注意

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 如需詳細資訊,請參閱本文稍後 的處理未完成的非同步動作 一節。

元件在 Razor 中的元件生命週期事件 Blazor

檔物件模型 (DOM) 事件處理:

  1. 事件處理常式正在執行。
  2. 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 呈現所有同步工作並完成 Task

檔物件模型 (DOM) 事件處理

Render生命週期:

  1. 避免在元件上進行進一步的轉譯作業:
    • 第一次轉譯之後。
    • ShouldRenderfalse 時。
  2. 建置轉譯樹狀結構差異 (差異) 並轉譯元件。
  3. 等候 DOM 更新。
  4. 呼叫 OnAfterRender{Async}

轉譯生命週期

開發人員呼叫 以 StateHasChanged 產生轉譯。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

設定參數時 (SetParametersAsync)

SetParametersAsync 會設定元件在轉譯樹狀結構或路由參數中由元件的父代提供的參數。

方法的參數 ParameterView 包含每次 SetParametersAsync 呼叫時元件的元件參數值集合。 藉由覆寫 SetParametersAsync 方法,開發人員程式碼可以直接與 ParameterView 的參數互動。

的預設實作 SetParametersAsync 會使用 [Parameter] 中具有對應值的 或[CascadingParameter] 屬性,設定每個屬性的值 ParameterView 。 中 ParameterView 沒有對應值的參數會保持不變。

如果未 base.SetParametersAsync 叫用,開發人員程式碼就可以以任何方式解譯傳入參數的值。 例如,不需要將傳入參數指派給 類別的屬性。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

在下列範例中,如果 剖析 的路由參數成功, ParameterView.TryGetValue 請將 參數 Param 的值指派 Paramvalue 。 當 不是 nullvalue ,元件會顯示值。

雖然 路由參數比對不區分大小寫TryGetValue 但只會比對路由範本中的區分大小寫參數名稱。 下列範例需要在 /{Param?} 路由範本中使用 ,才能取得具有 TryGetValue 的值,而不是 /{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當元件在 中 SetParametersAsync 收到其初始參數之後,就會叫用 和 OnInitializedAsync

針對同步作業,請覆寫 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 ...
}

Blazor在伺服器上預先呈現其內容的應用程式會呼叫 OnInitializedAsync兩次

  • 一旦元件一開始以靜態方式呈現為頁面的一部分時。
  • 瀏覽器轉譯元件的第二次。

若要防止開發人員程式碼在 OnInitializedAsync 預先呈現時執行兩次,請參閱 預先呈現之後的具狀態重新連線 一節。 雖然區段中的內容著重于 Blazor Server 可 SignalR 設定狀態的重新連線,但在託管應用程式中預先 Blazor WebAssembly 呈現的案例 (WebAssemblyPrerendered) 牽涉到類似的條件和方法,以避免執行開發人員程式碼兩次。 若要在預先呈現期間保留初始化程式碼執行期間的狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

Blazor雖然應用程式預先呈現,但無法呼叫 JavaScript (Interop) JS 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。 如需詳細資訊,請參閱 偵測應用程式預先呈現時 一節。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

設定參數之後 (OnParametersSet{Async})

OnParametersSetOnParametersSetAsync 稱為:

  • 在 或 OnInitializedAsyncOnInitialized 初始化元件之後。

  • 當父元件重新呈現並提供時:

    • 至少有一個參數變更時,已知或基本類型不可變。
    • 複雜型別參數。 架構無法知道複雜類型參數的值是否在內部變動,因此當有一或多個複雜類型的參數時,架構一律會將參數集視為變更。

    如需轉譯慣例的詳細資訊,請參閱ASP.NET Core Razor 元件轉譯

針對下列範例元件,流覽至 URL 上的元件頁面:

  • 使用 所 StartDate 收到的開始日期: /on-parameters-set/2021-03-19
  • 沒有開始日期,其中 StartDate 會指派目前當地時間的值: /on-parameters-set

Pages/OnParamsSet.razor:

注意

在元件路由中,您無法同時使用路由條件約束 datetime來限制 DateTime 參數,並讓參數成為選擇性。 因此,下列 OnParamsSet 元件會使用兩 @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 ...
}

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

如需路由參數和條件約束的詳細資訊,請參閱ASP.NET Core Blazor 路由和導覽

元件轉譯後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync 會在元件完成轉譯之後呼叫。 此時會填入元素和元件參考。 使用此階段可對轉譯的內容執行其他初始化步驟,例如 JS 與轉譯 DOM 元素互動的 Interop 呼叫。

OnAfterRenderAsync 的參數 firstRenderOnAfterRender

  • 設定為 true 第一次轉譯元件實例。
  • 可用來確保初始化工作只會執行一次。

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

即使您從 傳 OnAfterRenderAsyncTask ,架構也不會在該工作完成之後,為元件排程進一步的轉譯週期。 這是為了避免無限轉譯迴圈。 這與其他生命週期方法不同,它會在傳回完成後排程進一步的 Task 轉譯週期。

OnAfterRenderOnAfterRenderAsync不會在伺服器上的預先呈現程式期間呼叫。 在預先呈現元件之後以互動方式轉譯時,會呼叫 方法。 當應用程式預先呈現時:

  1. 元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標籤。 在此階段中, OnAfterRender 不會呼叫 和 OnAfterRenderAsync
  2. Blazor當腳本在瀏覽器中 (blazor.webassembly.jsblazor.server.js) 啟動時,元件會以互動式轉譯模式重新開機。 重新開機元件之後, OnAfterRender會呼叫OnAfterRenderAsync 因為應用程式不再處於預先呈現階段。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

狀態變更 (StateHasChanged)

StateHasChanged 通知元件其狀態已變更。 如果適用,呼叫 StateHasChanged 會導致重新呈現元件。

StateHasChanged 會自動針對 EventCallback 方法呼叫 。 如需事件回呼的詳細資訊,請參閱ASP.NET Core Blazor 事件處理

如需元件轉譯和何時呼叫 StateHasChanged 的詳細資訊,包括何時使用 叫用 ComponentBase.InvokeAsync 元件,請參閱ASP.NET Core Razor 元件轉譯

在轉譯時處理不完整的非同步動作

在生命週期事件中執行的非同步動作在轉譯元件之前可能尚未完成。 當生命週期方法執行時,物件可能會 null 或不完整地填入資料。 提供轉譯邏輯以確認物件已初始化。 例如,當 物件為 null 時,轉譯預留位置 UI 元素 (,載入訊息) 。

FetchData 範本的元件中 Blazor , OnInitializedAsync 會覆寫為非同步接收預測資料 (forecasts) 。 當 為 nullforecasts ,載入訊息會顯示給使用者。 Task完成傳 OnInitializedAsync 回的 之後,元件會以更新的狀態重新呈現。

Pages/FetchData.razor 在範本中 Blazor Server :

@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 應用程式中的錯誤

預先呈現後具狀態重新連線

當 為 ServerPrerenderedRenderMode , Blazor Server 在應用程式中,元件一開始會以靜態方式呈現為頁面的一部分。 一旦瀏覽器建立 SignalR 與伺服器的連線,元件就會 再次 轉譯並互動。 OnInitialized{Async}如果元件初始化的生命週期方法存在,則會執行方法兩次

  • 當元件以靜態方式預先呈現時。
  • 建立伺服器連線之後。

這可能會導致最終轉譯元件時,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) 牽涉到類似的條件和方法來防止執行開發人員程式碼兩次。 若要在執行預先呈現程式碼期間保留狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

偵測應用程式何時預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式會使用 呼叫 JSRuntimeExtensions.InvokeVoidAsync ,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

若要延遲 JavaScript Interop 呼叫,直到保證這類呼叫能夠運作的點為止,請覆寫OnAfterRender{Async} 生命週期事件。 只有在應用程式完全轉譯之後,才會呼叫此事件。

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 Interop 作為元件初始化邏輯的一部分,以與預先呈現相容的方式。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 時 JSRuntime.InvokeAsyncElementReference 只會在任何先前的生命週期方法中使用 OnAfterRenderAsync ,因為在轉譯元件之後,沒有 JavaScript 元素。

StateHasChanged會呼叫 以重新呈現元件,其中包含從 JavaScript Interop 呼叫取得的新狀態 (,以取得詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 是 nulldata 才會呼叫。

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

使用 和 進行 IDisposable 元件處置 IAsyncDisposable

如果元件實作 IDisposableIAsyncDisposable 或兩者,當元件從 UI 移除時,架構會呼叫 Unmanaged 資源處置。 處置可以隨時發生,包括 元件初始化期間。

元件不應該同時實 IDisposable 作 和 IAsyncDisposable 。 如果兩者都實作,架構只會執行非同步多載。

開發人員程式碼必須確保 IAsyncDisposable 實作不需要很長的時間才能完成。

同步 IDisposable

針對同步處置工作,請使用 IDisposable.Dispose

下列元件:

  • 使用 指示詞實作 IDisposable@implementsRazor 。
  • obj處置 ,這是實作 IDisposable 的 Unmanaged 型別。
  • 執行 Null 檢查是因為 obj 在生命週期方法中建立, (未顯示) 。
@implements IDisposable

...

@code {
    ...

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

如果單一物件需要處置,則呼叫 時 Dispose ,可以使用 Lambda 來處置物件。 下列範例會出現在ASP.NET Core Razor 元件轉譯文章中,並示範如何使用 Lambda 運算式來處置 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

下列元件:

  • 使用 指示詞實作 IAsyncDisposable@implementsRazor 。
  • obj處置 ,這是實 IAsyncDisposable 作 的 Unmanaged 型別。
  • 因為是在生命週期方法中建立的,所以會執行 obj null 檢查, (未顯示) 。
@implements IAsyncDisposable

...

@code {
    ...

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

如需詳細資訊,請參閱

null指派給已處置的物件

通常,呼叫 Dispose/DisposeAsync 之後就不需要指派 null 給處置的物件。 指派 null 的罕見案例包括:

  • 如果物件的型別實作不佳,而且無法容許重複呼叫 Dispose/DisposeAsync ,請在處置之後指派 null ,以正常略過對 Dispose/DisposeAsync 的進一步呼叫。
  • 如果長時間存留的進程繼續保存已處置物件的參考,指派 null 可讓 垃圾收集行程 釋放物件,同時保存該物件的參考。

這些是不尋常的案例。 對於正確實作且正常運作的物件,指派給已處置的物件沒有點 null 。 在必須指派 null 物件的罕見情況下,建議您記錄原因,並尋求解決方案,以避免需要指派 null

StateHasChanged

注意

不支援在 中 Dispose 呼叫 StateHasChangedStateHasChanged 可能會在終止轉譯器時叫用,因此不支援在該時間點要求 UI 更新。

事件處理常式

一律從 .NET 事件取消訂閱事件處理常式。 下列Blazor 表單範例示範如何在 方法中 Dispose 取消訂閱事件處理常式:

  • 私人欄位和 Lambda 方法

    @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 作和取消訂閱委派。 不過,當 公開事件的物件超過註冊委派的元件存留期時,無法取消訂閱委派是個問題。 發生這種情況時,記憶體流失會產生,因為已註冊的委派會讓原始物件保持運作。 因此,只有在您知道事件委派快速處置時,才使用下列方法。 當不確定需要處置的物件存留期時,請訂閱委派方法,並如先前範例所示正確地處置委派。

  • 匿名 Lambda 方法 (明確處置不需要) :

    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);
    }
    
  • 匿名 Lambda 運算式方法 (明確處置不需要) :

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

    具有匿名 Lambda 運算式的上述程式碼完整範例會出現在ASP.NET Core Blazor 表單和驗證一文中。

如需詳細資訊,請參閱 清除 Unmanaged 資源 及其後續 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. 如果元件第一次在要求上轉譯:
  2. 呼叫 OnParametersSet{Async}。 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 呈現所有同步工作並完成 Task

注意

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 如需詳細資訊,請參閱本文稍後 的處理未完成的非同步動作 一節。

元件在 Razor 中的元件生命週期事件 Blazor

檔物件模型 (DOM) 事件處理:

  1. 事件處理常式正在執行。
  2. 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 呈現所有同步工作並完成 Task

檔物件模型 (DOM) 事件處理

Render生命週期:

  1. 避免在元件上進行進一步的轉譯作業:
    • 第一次轉譯之後。
    • ShouldRenderfalse 時。
  2. 建置轉譯樹狀結構差異 (差異) 並轉譯元件。
  3. 等候 DOM 更新。
  4. 呼叫 OnAfterRender{Async}

轉譯生命週期

開發人員呼叫 以 StateHasChanged 產生轉譯。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

設定參數時 (SetParametersAsync)

SetParametersAsync 會設定元件在轉譯樹狀結構或路由參數中由元件的父代提供的參數。

方法的參數 ParameterView 包含每次 SetParametersAsync 呼叫時元件的元件參數值集合。 藉由覆寫 SetParametersAsync 方法,開發人員程式碼可以直接與 ParameterView 的參數互動。

的預設實作 SetParametersAsync 會使用 [Parameter] 中具有對應值的 或[CascadingParameter] 屬性,設定每個屬性的值 ParameterView 。 中 ParameterView 沒有對應值的參數會保持不變。

如果未 base.SetParametersAsync 叫用,開發人員程式碼就可以以任何方式解譯傳入參數的值。 例如,不需要將傳入參數指派給 類別的屬性。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

在下列範例中,如果 剖析 的路由參數成功, ParameterView.TryGetValue 請將 參數 Param 的值指派 Paramvalue 。 當 不是 nullvalue ,元件會顯示值。

雖然 路由參數比對不區分大小寫, TryGetValue 但只會比對路由範本中的區分大小寫參數名稱。 下列範例需要使用 /{Param?} 路由範本中的 ,才能使用 TryGetValue 取得 的值,而不是 /{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當元件在 中收到其初始參數 SetParametersAsync 之後,就會叫用 和 OnInitializedAsync

針對同步作業,請覆寫 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 ...
}

Blazor在伺服器上預先呈現其內容的應用程式會呼叫 OnInitializedAsync兩次

  • 一旦元件一開始以靜態方式呈現為頁面的一部分時。
  • 當瀏覽器轉譯元件時,第二次。

若要防止開發人員程式碼在 OnInitializedAsync 預先呈現時執行兩次,請參閱 預先呈現後具狀態重新連線一 節。 雖然區段中的內容著重于 Blazor Server 和可 SignalR 設定狀態的重新連線,但 (裝載應用程式中預先呈現 Blazor WebAssembly 的 WebAssemblyPrerendered 案例) 牽涉到類似的條件和方法,以防止執行開發人員程式碼兩次。 若要在執行預先呈現程式碼期間保留狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

Blazor雖然應用程式預先呈現,但無法呼叫 JavaScript (Interop) JS 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。 如需詳細資訊,請參閱 在應用程式預先呈現時偵測 一節。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

設定參數之後 (OnParametersSet{Async})

OnParametersSetOnParametersSetAsync 稱為:

  • 在 或 OnInitializedAsyncOnInitialized 初始化元件之後。

  • 當父元件重新呈現並提供時:

    • 至少有一個參數已變更時,已知或基本不可變的類型。
    • 複雜型別參數。 架構無法知道複雜型別參數的值是否在內部變動,因此當一或多個複雜型別參數存在時,架構一律會將設定為變更的參數。

    如需轉譯慣例的詳細資訊,請參閱ASP.NET Core Razor 元件轉譯

針對下列範例元件,流覽至 URL 上的元件頁面:

  • 從 接收 StartDate 的開始日期: /on-parameters-set/2021-03-19
  • 沒有開始日期,其中 StartDate 會指派目前當地時間的值: /on-parameters-set

Pages/OnParamsSet.razor:

注意

在元件路由中,您無法同時使用路由條件約束 datetime來限制 DateTime 參數,並讓參數成為選擇性。 因此,下列 OnParamsSet 元件會使用兩 @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 ...
}

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

如需路由參數和條件約束的詳細資訊,請參閱ASP.NET Core Blazor 路由和導覽

元件轉譯後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync 會在元件完成轉譯之後呼叫。 此時會填入元素和元件參考。 使用此階段來執行轉譯內容的其他初始化步驟,例如 JS 與轉譯的 DOM 元素互動的 Interop 呼叫。

firstRenderOnAfterRenderAsync 的參數 OnAfterRender

  • 第一次轉譯元件實例時,會設定為 true
  • 可用來確保初始化工作只會執行一次。

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 轉譯週期。

OnAfterRenderOnAfterRenderAsync不會在伺服器上的預先呈現程式期間呼叫。 在預先呈現元件之後以互動方式轉譯時,會呼叫 方法。 當應用程式預先呈現時:

  1. 元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標籤。 在此階段中, OnAfterRender 不會呼叫 和 OnAfterRenderAsync
  2. Blazor當腳本在瀏覽器中 (blazor.webassembly.jsblazor.server.js) 啟動時,元件會以互動式轉譯模式重新開機。 重新開機元件之後, OnAfterRender會呼叫OnAfterRenderAsync 因為應用程式不再處於預先呈現階段。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

狀態變更 (StateHasChanged)

StateHasChanged 通知元件其狀態已變更。 如果適用,呼叫 StateHasChanged 會導致重新呈現元件。

StateHasChanged 會自動針對 EventCallback 方法呼叫 。 如需事件回呼的詳細資訊,請參閱ASP.NET Core Blazor 事件處理

如需元件轉譯和何時呼叫 StateHasChanged 的詳細資訊,包括何時使用 叫用 ComponentBase.InvokeAsync 元件,請參閱ASP.NET Core Razor 元件轉譯

在轉譯時處理不完整的非同步動作

在生命週期事件中執行的非同步動作在轉譯元件之前可能尚未完成。 當生命週期方法執行時,物件可能會 null 或不完整地填入資料。 提供轉譯邏輯以確認物件已初始化。 例如,當 物件為 null 時,轉譯預留位置 UI 元素 (,載入訊息) 。

FetchData 範本的元件中 Blazor , OnInitializedAsync 會覆寫為非同步接收預測資料 (forecasts) 。 當 為 nullforecasts ,載入訊息會顯示給使用者。 Task完成傳 OnInitializedAsync 回的 之後,元件會以更新的狀態重新呈現。

Pages/FetchData.razor 在範本中 Blazor Server :

@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 應用程式中的錯誤

預先呈現後具狀態重新連線

當 為 ServerPrerenderedRenderMode , Blazor Server 在應用程式中,元件一開始會以靜態方式呈現為頁面的一部分。 一旦瀏覽器建立 SignalR 與伺服器的連線,元件就會 再次 轉譯並互動。 OnInitialized{Async}如果元件初始化的生命週期方法存在,則會執行方法兩次

  • 當元件以靜態方式預先呈現時。
  • 建立伺服器連線之後。

這可能會導致最終轉譯元件時,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) 牽涉到類似的條件和方法來防止執行開發人員程式碼兩次。 若要在執行預先呈現程式碼期間保留狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

偵測應用程式何時預先呈現

本節適用于 Blazor Server 預先呈現 Razor 元件的裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式會使用 呼叫 JSRuntimeExtensions.InvokeVoidAsync ,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

若要延遲 JavaScript Interop 呼叫,直到保證這類呼叫能夠運作的點為止,請覆寫OnAfterRender{Async} 生命週期事件。 只有在應用程式完全轉譯之後,才會呼叫此事件。

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 Interop 作為元件初始化邏輯的一部分,以與預先呈現相容的方式。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 時 JSRuntime.InvokeAsyncElementReference 只會在任何先前的生命週期方法中使用 OnAfterRenderAsync ,因為在轉譯元件之後,沒有 JavaScript 元素。

StateHasChanged會呼叫 以重新呈現元件,其中包含從 JavaScript Interop 呼叫取得的新狀態 (,以取得詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 是 nulldata 才會呼叫。

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

使用 和 進行 IDisposable 元件處置 IAsyncDisposable

如果元件實作 IDisposableIAsyncDisposable 或兩者,當元件從 UI 移除時,架構會呼叫 Unmanaged 資源處置。 處置可以隨時發生,包括 元件初始化期間。

元件不應該同時實 IDisposable 作 和 IAsyncDisposable 。 如果兩者都實作,架構只會執行非同步多載。

開發人員程式碼必須確保 IAsyncDisposable 實作不需要很長的時間才能完成。

同步 IDisposable

針對同步處置工作,請使用 IDisposable.Dispose

下列元件:

  • 使用 指示詞實作 IDisposable@implementsRazor 。
  • obj處置 ,這是實作 IDisposable 的 Unmanaged 型別。
  • 執行 Null 檢查是因為 obj 在生命週期方法中建立, (未顯示) 。
@implements IDisposable

...

@code {
    ...

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

如果單一物件需要處置,則呼叫 時 Dispose ,可以使用 Lambda 來處置物件。 下列範例會出現在ASP.NET Core Razor 元件轉譯文章中,並示範如何使用 Lambda 運算式來處置 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

下列元件:

@implements IAsyncDisposable

...

@code {
    ...

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

如需詳細資訊,請參閱

null指派給已處置的物件

通常呼叫 之後 Dispose/DisposeAsync ,不需要指派 null 給已處置的物件。 指派 null 的罕見案例包括:

  • 如果物件的型別實作不佳,而且無法容忍對 Dispose/DisposeAsync 的重複呼叫,請在處置之後指派 null ,以正常地略過對 的 Dispose/DisposeAsync 進一步呼叫。
  • 如果長時間存留的進程繼續保存已處置物件的參考,指派 null 可讓 垃圾收集行程 釋放物件,儘管長期進程保存該物件的參考。

這些是不尋常的案例。 對於正確實作且正常運作的物件,沒有指派 null 給已處置物件的點。 在必須指派 null 物件的罕見情況下,建議您記錄原因,並尋找防止指派 null 的解決方案。

StateHasChanged

注意

不支援在 中 Dispose 呼叫 StateHasChangedStateHasChanged 可能會在終止轉譯器時叫用,因此不支援在該時間點要求 UI 更新。

事件處理常式

一律從 .NET 事件取消訂閱事件處理常式。 下列Blazor 表單範例示範如何在 方法中 Dispose 取消訂閱事件處理常式:

  • 私人欄位和 Lambda 方法

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

如需詳細資訊,請參閱使用 和 IAsyncDisposableIDisposable 元件處置一節。

匿名函式、方法和運算式

使用 匿名函式、方法或運算式時,不需要實 IDisposable 作和取消訂閱委派。 不過,當 公開事件的物件超過註冊委派的元件存留期時,無法取消訂閱委派是個問題。 發生這種情況時,記憶體流失結果是因為已註冊的委派會讓原始物件保持運作。 因此,只有在您知道事件委派快速處置時,才使用下列方法。 當不確定需要處置的物件存留期時,請訂閱委派方法,並如先前範例所示正確處置委派。

  • 匿名 Lambda 方法方法 (不需要明確處置) :

    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);
    }
    
  • 匿名 Lambda 運算式方法 (不需要明確處置) :

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

    具有匿名 Lambda 運算式的上述程式碼完整範例會出現在ASP.NET Core Blazor 表單和驗證一文中。

如需詳細資訊,請參閱 清除 Unmanaged 資源 及其後續實作 DisposeDisposeAsync 方法的主題。

可取消的背景工作

元件通常會執行長時間執行的背景工作,例如 (網路呼叫) 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. 如果元件第一次在要求上轉譯:
  2. 呼叫 OnParametersSet{Async}。 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 轉譯所有同步工作並完成 Task

注意

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 如需詳細資訊,請參閱本文稍後的 處理未完成的非同步動作 一節。

元件在 中的元件生命週期事件 RazorBlazor

檔物件模型 (DOM) 事件處理:

  1. 事件處理常式正在執行。
  2. 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 轉譯所有同步工作並完成 Task

檔物件模型 (DOM) 事件處理

Render生命週期:

  1. 避免在元件上進行進一步的轉譯作業:
    • 第一次轉譯之後。
    • ShouldRenderfalse 時。
  2. 建置轉譯樹狀結構差異 (差異) 並轉譯元件。
  3. 等候 DOM 更新。
  4. 呼叫 OnAfterRender{Async}

轉譯生命週期

開發人員呼叫 以 StateHasChanged 產生轉譯。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

設定參數時 (SetParametersAsync)

SetParametersAsync 會從轉譯樹狀結構或路由參數中,設定元件父代所提供的參數。

方法的參數 ParameterView 包含每次 SetParametersAsync 呼叫元件的元件參數值集。 藉由覆寫 SetParametersAsync 方法,開發人員程式碼可以直接與 ParameterView 的參數互動。

的預設實作會 SetParametersAsync 使用 [Parameter] 中具有對應值的 或[CascadingParameter] 屬性,設定每個屬性的值 ParameterView 。 中 ParameterView 沒有對應值的參數會保持不變。

如果未 base.SetParametersAsync 叫用,開發人員程式碼就可以以任何必要方式解譯傳入參數的值。 例如,不需要將傳入參數指派給 類別的屬性。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

在下列範例中,如果 剖析 的路由參數 Param 成功, ParameterView.TryGetValue 請將 參數的值指派 Paramvalue 。 當 不是 nullvalue ,元件會顯示值。

雖然 路由參數比對不區分大小寫, TryGetValue 但只會比對路由範本中的區分大小寫參數名稱。 下列範例需要使用 /{Param?} 路由範本中的 ,才能使用 TryGetValue 取得 的值,而不是 /{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當元件在 中收到其初始參數 SetParametersAsync 之後,就會叫用 和 OnInitializedAsync

針對同步作業,請覆寫 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 ...
}

Blazor在伺服器上預先呈現其內容的應用程式會呼叫 OnInitializedAsync兩次

  • 一旦元件一開始以靜態方式呈現為頁面的一部分時。
  • 當瀏覽器轉譯元件時,第二次。

若要防止開發人員程式碼在 OnInitializedAsync 預先呈現時執行兩次,請參閱 預先呈現後具狀態重新連線一 節。 雖然區段中的內容著重于 Blazor Server 和可 SignalR 設定狀態的重新連線,但 (裝載應用程式中預先呈現 Blazor WebAssembly 的 WebAssemblyPrerendered 案例) 牽涉到類似的條件和方法,以防止執行開發人員程式碼兩次。 若要在執行預先呈現程式碼期間保留狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

Blazor雖然應用程式預先呈現,但無法呼叫 JavaScript (Interop) JS 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。 如需詳細資訊,請參閱 在應用程式預先呈現時偵測 一節。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

設定參數之後 (OnParametersSet{Async})

OnParametersSetOnParametersSetAsync 稱為:

  • 在 或 OnInitializedAsyncOnInitialized 初始化元件之後。

  • 當父元件重新呈現並提供時:

    • 至少有一個參數已變更時,已知或基本不可變的類型。
    • 複雜型別參數。 架構無法知道複雜型別參數的值是否在內部變動,因此當一或多個複雜型別參數存在時,架構一律會將設定為變更的參數。

    如需轉譯慣例的詳細資訊,請參閱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 ...
}

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

如需路由參數和條件約束的詳細資訊,請參閱ASP.NET Core Blazor 路由和導覽

元件轉譯後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync 會在元件完成轉譯之後呼叫。 此時會填入元素和元件參考。 使用此階段來執行轉譯內容的其他初始化步驟,例如 JS 與轉譯的 DOM 元素互動的 Interop 呼叫。

firstRenderOnAfterRenderAsync 的參數 OnAfterRender

  • 第一次轉譯元件實例時,會設定為 true
  • 可用來確保初始化工作只會執行一次。

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 轉譯週期。

OnAfterRenderOnAfterRenderAsync不會在伺服器上的預先呈現程式期間呼叫。 在預先呈現元件之後以互動方式轉譯時,會呼叫 方法。 當應用程式預先呈現時:

  1. 元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標籤。 在此階段中, OnAfterRender 不會呼叫 和 OnAfterRenderAsync
  2. 當 Blazor 腳本 (blazor.webassembly.jsblazor.server.js) 在瀏覽器中啟動時,元件會以互動式轉譯模式重新開機。 重新開機元件之後, OnAfterRenderOnAfterRenderAsync 呼叫 ,因為應用程式不再處於預先呈現階段。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

狀態變更 (StateHasChanged)

StateHasChanged 通知元件其狀態已變更。 如果適用,呼叫 StateHasChanged 會導致重新呈現元件。

StateHasChanged 會針對方法自動呼叫 EventCallback 。 如需事件回呼的詳細資訊,請參閱ASP.NET Core Blazor 事件處理

如需元件轉譯和何時呼叫 StateHasChanged 的詳細資訊,包括何時使用 叫用 ComponentBase.InvokeAsync 元件,請參閱ASP.NET Core Razor 元件轉譯

在轉譯時處理不完整的非同步動作

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 當生命週期方法執行時,物件可能會 null 或不完整地填入資料。 提供轉譯邏輯,以確認物件已初始化。 例如,當 物件為 null 時,轉譯預留位置 UI 元素 (載入訊息) 。

FetchData 範本的 元件中 Blazor , OnInitializedAsync 會覆寫為非同步接收預測資料 (forecasts) 。 當 為 nullforecasts ,載入訊息會顯示給使用者。 Task在 傳回的 OnInitializedAsync 完成之後,元件會以更新的狀態重新呈現。

Pages/FetchData.razor 在範本中 Blazor Server :

@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 應用程式中的錯誤

預先呈現之後的具狀態重新連線

當 為 ServerPrerenderedRenderMode , Blazor Server 元件一開始會以靜態方式呈現為頁面的一部分。 一旦瀏覽器建立 SignalR 與伺服器的連線,元件就會 再次 轉譯且互動式。 OnInitialized{Async}如果元件初始化的生命週期方法存在,則會執行方法兩次

  • 以靜態方式預先呈現元件時。
  • 建立伺服器連接之後。

當元件最終轉譯時,這可能會導致 UI 中顯示的資料有明顯的變更。 若要避免在應用程式中執行 Blazor Server 這個雙重轉譯行為,請在預先呈現期間傳入識別碼來快取狀態,並在預先呈現之後擷取狀態。

下列程式碼示範範本型 Blazor Server 應用程式中的更新 WeatherForecastService ,以避免雙重轉譯。 在下列範例中,等候 Delayawait 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) 牽涉到類似的條件和方法,以避免執行開發人員程式碼兩次。 若要在預先呈現期間保留初始化程式碼執行期間的狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

偵測應用程式在預先呈現時

本節適用于 Blazor Server 預先呈現 Razor 元件的 裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式是使用 JSRuntimeExtensions.InvokeVoidAsync 呼叫,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

若要延遲 JavaScript Interop 呼叫,直到保證這類呼叫能夠運作的點為止,請覆寫OnAfterRender{Async} 生命週期事件。 只有在應用程式完全轉譯之後,才會呼叫此事件。

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 Interop 作為元件初始化邏輯的一部分,以與預先呈現相容的方式。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 時 JSRuntime.InvokeAsyncElementReference 只會在 和 之前的任何生命週期方法中使用 OnAfterRenderAsync ,因為直到轉譯元件之後,才有 JavaScript 元素。

StateHasChanged會呼叫 ,以重新呈現從 JavaScript Interop 呼叫取得的新狀態 (元件,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 為 nulldata 才會呼叫 。

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

搭配 和 的 IDisposable 元件處置 IAsyncDisposable

如果元件實作 IDisposableIAsyncDisposable 或兩者,當元件從 UI 移除時,架構會呼叫 Unmanaged 資源處置。 您可以隨時進行處置,包括 元件初始化期間。

元件不應該同時實 IDisposable 作 和 IAsyncDisposable 。 如果兩者都已實作,架構只會執行非同步多載。

開發人員程式碼必須確保 IAsyncDisposable 實作不需要很長的時間才能完成。

同步 IDisposable

針對同步處置工作,請使用 IDisposable.Dispose

下列元件:

  • IDisposable使用 指示詞實作 @implementsRazor 。
  • obj處置 ,這是實 IDisposable 作 的 Unmanaged 型別。
  • 因為是在生命週期方法中建立的,所以會執行 obj null 檢查, (未顯示) 。
@implements IDisposable

...

@code {
    ...

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

如果單一物件需要處置,則呼叫 時 Dispose ,可以使用 Lambda 來處置物件。 下列範例會出現在ASP.NET Core Razor 元件轉譯一文中,並示範如何使用 Lambda 運算式來處置 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

下列元件:

  • IAsyncDisposable使用 指示詞實作 @implementsRazor 。
  • obj處置 ,這是實 IAsyncDisposable 作 的 Unmanaged 型別。
  • 因為是在生命週期方法中建立的,所以會執行 obj null 檢查, (未顯示) 。
@implements IAsyncDisposable

...

@code {
    ...

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

如需詳細資訊,請參閱

null指派給已處置的物件

通常呼叫 之後 Dispose/DisposeAsync ,不需要指派 null 給已處置的物件。 指派 null 的罕見案例包括:

  • 如果物件的型別實作不佳,而且無法容忍對 Dispose/DisposeAsync 的重複呼叫,請在處置之後指派 null ,以正常地略過對 的 Dispose/DisposeAsync 進一步呼叫。
  • 如果長時間存留的進程繼續保存已處置物件的參考,指派 null 可讓 垃圾收集行程 釋放物件,儘管長期進程保存該物件的參考。

這些是不尋常的案例。 對於正確實作且正常運作的物件,沒有指派 null 給已處置物件的點。 在必須指派 null 物件的罕見情況下,建議您記錄原因,並尋找防止指派 null 的解決方案。

StateHasChanged

注意

不支援在 中 Dispose 呼叫 StateHasChangedStateHasChanged 可能會在終止轉譯器時叫用,因此不支援在該時間點要求 UI 更新。

事件處理常式

一律從 .NET 事件取消訂閱事件處理常式。 下列Blazor 表單範例示範如何在 方法中 Dispose 取消訂閱事件處理常式:

  • 私人欄位和 Lambda 方法

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

如需詳細資訊,請參閱使用 和 IAsyncDisposableIDisposable 元件處置一節。

匿名函式、方法和運算式

使用 匿名函式、方法或運算式時,不需要實 IDisposable 作和取消訂閱委派。 不過,當 公開事件的物件超過註冊委派的元件存留期時,無法取消訂閱委派是個問題。 發生這種情況時,記憶體流失結果是因為已註冊的委派會讓原始物件保持運作。 因此,只有在您知道事件委派快速處置時,才使用下列方法。 當不確定需要處置的物件存留期時,請訂閱委派方法,並如先前範例所示正確處置委派。

  • 匿名 Lambda 方法方法 (不需要明確處置) :

    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);
    }
    
  • 匿名 Lambda 運算式方法 (不需要明確處置) :

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

    具有匿名 Lambda 運算式的上述程式碼完整範例會出現在ASP.NET Core Blazor 表單和驗證一文中。

如需詳細資訊,請參閱 清除 Unmanaged 資源 及其後續實作 DisposeDisposeAsync 方法的主題。

可取消的背景工作

元件通常會執行長時間執行的背景工作,例如 (網路呼叫) 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 指引

元件會在 Razor 一組同步和非同步生命週期方法中處理 Razor 元件生命週期事件。 您可以在元件初始化和轉譯期間覆寫生命週期方法,以在元件中執行其他作業。

週期事件

下圖說明 Razor 元件生命週期事件。 與生命週期事件相關聯的 C# 方法會以本文下列各節中的範例定義。

元件生命週期事件:

  1. 如果元件第一次在要求上轉譯:
  2. 呼叫 OnParametersSet{Async}。 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 轉譯所有同步工作並完成 Task

注意

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 如需詳細資訊,請參閱本文稍後的 處理未完成的非同步動作 一節。

元件在 中的元件生命週期事件 RazorBlazor

檔物件模型 (DOM) 事件處理:

  1. 事件處理常式正在執行。
  2. 如果傳回不完整 TaskTask 則會等候 ,然後重新呈現元件。
  3. 轉譯所有同步工作並完成 Task

檔物件模型 (DOM) 事件處理

Render生命週期:

  1. 避免在元件上進行進一步的轉譯作業:
    • 第一次轉譯之後。
    • ShouldRenderfalse 時。
  2. 建置轉譯樹狀結構差異 (差異) 並轉譯元件。
  3. 等候 DOM 更新。
  4. 呼叫 OnAfterRender{Async}

轉譯生命週期

開發人員呼叫 以 StateHasChanged 產生轉譯。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯

設定參數時 (SetParametersAsync)

SetParametersAsync 會從轉譯樹狀結構或路由參數中,設定元件父代所提供的參數。

方法的參數 ParameterView 包含每次 SetParametersAsync 呼叫元件的元件參數值集。 藉由覆寫 SetParametersAsync 方法,開發人員程式碼可以直接與 ParameterView 的參數互動。

的預設實作會 SetParametersAsync 使用 [Parameter] 中具有對應值的 或[CascadingParameter] 屬性,設定每個屬性的值 ParameterView 。 中 ParameterView 沒有對應值的參數會保持不變。

如果未 base.SetParametersAsync 叫用,開發人員程式碼就可以以任何必要方式解譯傳入參數的值。 例如,不需要將傳入參數指派給 類別的屬性。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置一 IDisposableIAsyncDisposable節。

在下列範例中,如果 剖析 的路由參數 Param 成功, ParameterView.TryGetValue 請將 參數的值指派 Paramvalue 。 當 不是 nullvalue ,元件會顯示值。

雖然 路由參數比對不區分大小寫, TryGetValue 但只會比對路由範本中的區分大小寫參數名稱。 下列範例需要使用 /{Param?} 路由範本中的 ,才能使用 TryGetValue 取得 的值,而不是 /{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當元件在 中 SetParametersAsync 收到其初始參數之後,就會叫用 和 OnInitializedAsync

針對同步作業,請覆寫 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 ...
}

Blazor在伺服器上預先呈現其內容的應用程式會呼叫 OnInitializedAsync兩次

  • 一旦元件一開始以靜態方式呈現為頁面的一部分時。
  • 瀏覽器轉譯元件的第二次。

若要防止開發人員程式碼在 OnInitializedAsync 預先呈現時執行兩次,請參閱 預先呈現之後的具狀態重新連線 一節。 雖然區段中的內容著重于 Blazor Server 可 SignalR 設定狀態的重新連線,但在託管應用程式中預先 Blazor WebAssembly 呈現的案例 (WebAssemblyPrerendered) 牽涉到類似的條件和方法,以避免執行開發人員程式碼兩次。 若要在預先呈現期間保留初始化程式碼執行期間的狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

Blazor雖然應用程式預先呈現,但無法呼叫 JavaScript (Interop) JS 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。 如需詳細資訊,請參閱 偵測應用程式預先呈現時 一節。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

設定參數之後 (OnParametersSet{Async})

OnParametersSetOnParametersSetAsync 稱為:

  • 在 或 OnInitializedAsyncOnInitialized 初始化元件之後。

  • 當父元件重新呈現並提供時:

    • 至少有一個參數變更時,已知或基本類型不可變。
    • 複雜型別參數。 架構無法知道複雜類型參數的值是否在內部變動,因此當有一或多個複雜類型的參數時,架構一律會將參數集視為變更。

    如需轉譯慣例的詳細資訊,請參閱ASP.NET Core Razor 元件轉譯

針對下列範例元件,流覽至 URL 上的元件頁面:

  • 使用 所 StartDate 收到的開始日期: /on-parameters-set/2021-03-19
  • 沒有開始日期,其中 StartDate 會指派目前當地時間的值: /on-parameters-set

Pages/OnParamsSet.razor:

注意

在元件路由中,您無法同時使用路由條件約束 datetime來限制 DateTime 參數,並讓參數成為選擇性。 因此,下列 OnParamsSet 元件會使用兩 @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 ...
}

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

如需路由參數和條件約束的詳細資訊,請參閱ASP.NET Core Blazor 路由和導覽

元件轉譯後 (OnAfterRender{Async})

OnAfterRenderOnAfterRenderAsync 會在元件完成轉譯之後呼叫。 此時會填入元素和元件參考。 使用此階段可對轉譯的內容執行其他初始化步驟,例如 JS 與轉譯 DOM 元素互動的 Interop 呼叫。

OnAfterRenderAsync 的參數 firstRenderOnAfterRender

  • 設定為 true 第一次轉譯元件實例。
  • 可用來確保初始化工作只會執行一次。

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

即使您從 傳 OnAfterRenderAsyncTask ,架構也不會在該工作完成之後,為元件排程進一步的轉譯週期。 這是為了避免無限轉譯迴圈。 這與其他生命週期方法不同,它會在傳回完成後排程進一步的 Task 轉譯週期。

OnAfterRenderOnAfterRenderAsync不會在伺服器上的預先呈現程式期間呼叫。 在預先呈現元件之後以互動方式轉譯時,會呼叫 方法。 當應用程式預先呈現時:

  1. 元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標籤。 在這個階段, OnAfterRender 不會呼叫 和 OnAfterRenderAsync
  2. 當 Blazor 腳本 (blazor.webassembly.jsblazor.server.js) 在瀏覽器中啟動時,元件會以互動式轉譯模式重新開機。 重新開機元件之後, OnAfterRenderOnAfterRenderAsync 呼叫 ,因為應用程式不再處於預先呈現階段。

如果在開發人員程式碼中提供事件處理常式,請在處置時將其解除hook。 如需詳細資訊,請參閱元件處置與 一 IDisposableIAsyncDisposable節。

狀態變更 (StateHasChanged)

StateHasChanged 通知元件其狀態已變更。 如果適用,呼叫 StateHasChanged 會導致重新呈現元件。

StateHasChanged 會針對方法自動呼叫 EventCallback 。 如需事件回呼的詳細資訊,請參閱ASP.NET Core Blazor 事件處理

如需元件轉譯和何時呼叫 StateHasChanged 的詳細資訊,包括何時使用 叫用 ComponentBase.InvokeAsync 元件,請參閱ASP.NET Core Razor 元件轉譯

在轉譯時處理不完整的非同步動作

在轉譯元件之前,生命週期事件中執行的非同步動作可能尚未完成。 當生命週期方法執行時,物件可能會 null 或不完整地填入資料。 提供轉譯邏輯,以確認物件已初始化。 例如,當 物件為 null 時,轉譯預留位置 UI 元素 (載入訊息) 。

FetchData 範本的 元件中 Blazor , OnInitializedAsync 會覆寫為非同步接收預測資料 (forecasts) 。 當 為 nullforecasts ,載入訊息會顯示給使用者。 Task在 傳回的 OnInitializedAsync 完成之後,元件會以更新的狀態重新呈現。

Pages/FetchData.razor 在範本中 Blazor Server :

@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(DateOnly.FromDateTime(DateTime.Now));
    }
}

處理錯誤

如需在生命週期方法執行期間處理錯誤的資訊,請參閱處理 ASP.NET Core Blazor 應用程式中的錯誤

預先呈現之後的具狀態重新連線

當 為 ServerPrerenderedRenderMode , Blazor Server 元件一開始會以靜態方式呈現為頁面的一部分。 一旦瀏覽器建立 SignalR 與伺服器的連線,元件就會 再次 轉譯且互動式。 OnInitialized{Async}如果元件初始化的生命週期方法存在,則會執行方法兩次

  • 以靜態方式預先呈現元件時。
  • 建立伺服器連接之後。

當元件最終轉譯時,這可能會導致 UI 中顯示的資料有明顯的變更。 若要避免在應用程式中執行 Blazor Server 這個雙重轉譯行為,請在預先呈現期間傳入識別碼來快取狀態,並在預先呈現之後擷取狀態。

下列程式碼示範範本型 Blazor Server 應用程式中的更新 WeatherForecastService ,以避免雙重轉譯。 在下列範例中,等候 Delayawait 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) 牽涉到類似的條件和方法,以避免執行開發人員程式碼兩次。 若要在預先呈現期間保留初始化程式碼執行期間的狀態,請參閱Prerender 並整合 ASP.NET Core Razor 元件

偵測應用程式在預先呈現時

本節適用于 Blazor Server 預先呈現 Razor 元件的 裝載 Blazor WebAssembly 應用程式。 預先呈現涵蓋在Prerender 中,並整合 ASP.NET Core Razor 元件

雖然應用程式預先呈現,但無法呼叫 JavaScript 等特定動作。 預先呈現時,元件可能需要以不同的方式呈現。

在下列範例中,函 setElementText1 式會放在 元素內 <head> 。 函式是使用 JSRuntimeExtensions.InvokeVoidAsync 呼叫,而且不會傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

若要延遲 JavaScript Interop 呼叫,直到保證這類呼叫能夠運作的點為止,請覆寫OnAfterRender{Async} 生命週期事件。 只有在應用程式完全轉譯之後,才會呼叫此事件。

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 Interop 作為元件初始化邏輯的一部分,以與預先呈現相容的方式。 元件顯示可以從 內部 OnAfterRenderAsync 觸發轉譯更新。 開發人員必須小心,以避免在此案例中建立無限迴圈。

在下列範例中,函 setElementText2 式會放在 元素內 <head> 。 函式會使用 呼叫 IJSRuntime.InvokeAsync ,並傳回值。

注意

如需位置和生產應用程式建議的 JS 一般指引,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS Interop)

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

警告

上述範例會直接修改 Document Object Model (DOM) 僅供示範之用。 在大部分情況下,不建議使用 JavaScript 直接修改 DOM,因為 JavaScript 可能會干擾 Blazor 變更追蹤。 如需詳細資訊,請參閱ASP.NET Core Blazor JavaScript 互通性 (JS interop)

呼叫 時 JSRuntime.InvokeAsyncElementReference 只會在 和 之前的任何生命週期方法中使用 OnAfterRenderAsync ,因為直到轉譯元件之後,才有 JavaScript 元素。

StateHasChanged會呼叫 ,以重新呈現從 JavaScript Interop 呼叫取得的新狀態 (元件,如需詳細資訊,請參閱ASP.NET Core Razor 元件轉譯) 。 程式碼不會建立無限迴圈,因為 StateHasChanged 只有在 為 nulldata 才會呼叫 。

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

搭配 和 的 IDisposable 元件處置 IAsyncDisposable

如果元件實作 IDisposableIAsyncDisposable 或兩者,當元件從 UI 移除時,架構會呼叫 Unmanaged 資源處置。 您可以隨時進行處置,包括 元件初始化期間。

元件不應該同時實 IDisposable 作 和 IAsyncDisposable 。 如果兩者都已實作,架構只會執行非同步多載。

開發人員程式碼必須確保 IAsyncDisposable 實作不需要很長的時間才能完成。

同步 IDisposable

針對同步處置工作,請使用 IDisposable.Dispose

下列元件:

  • IDisposable使用 指示詞實作 @implementsRazor 。
  • obj處置 ,這是實 IDisposable 作 的 Unmanaged 型別。
  • 因為是在生命週期方法中建立的,所以會執行 obj null 檢查, (未顯示) 。
@implements IDisposable

...

@code {
    ...

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

如果單一物件需要處置,則呼叫 時 Dispose ,可以使用 Lambda 來處置物件。 下列範例會出現在ASP.NET Core Razor 元件轉譯一文中,並示範如何使用 Lambda 運算式來處置 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

下列元件:

  • IAsyncDisposable使用 指示詞實作 @implementsRazor 。
  • obj處置 ,這是實 IAsyncDisposable 作 的 Unmanaged 型別。
  • 因為是在生命週期方法中建立的,所以會執行 obj null 檢查, (未顯示) 。
@implements IAsyncDisposable

...

@code {
    ...

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

如需詳細資訊,請參閱

null指派給已處置的物件

通常,呼叫 Dispose/DisposeAsync 之後就不需要指派 null 給處置的物件。 指派 null 的罕見案例包括:

  • 如果物件的型別實作不佳,而且無法容許重複呼叫 Dispose/DisposeAsync ,請在處置之後指派 null ,以正常略過對 Dispose/DisposeAsync 的進一步呼叫。
  • 如果長時間存留的進程繼續保存已處置物件的參考,指派 null 可讓 垃圾收集行程 釋放物件,同時保存該物件的參考。

這些是不尋常的案例。 對於正確實作且正常運作的物件,指派給已處置的物件沒有點 null 。 在必須指派 null 物件的罕見情況下,建議您記錄原因,並尋求解決方案,以避免需要指派 null

StateHasChanged

注意

不支援在 中 Dispose 呼叫 StateHasChangedStateHasChanged 可能會在終止轉譯器時叫用,因此不支援在該時間點要求 UI 更新。

事件處理常式

一律從 .NET 事件取消訂閱事件處理常式。 下列Blazor 表單範例示範如何在 方法中 Dispose 取消訂閱事件處理常式:

  • 私人欄位和 Lambda 方法

    @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 作和取消訂閱委派。 不過,當 公開事件的物件超過註冊委派的元件存留期時,無法取消訂閱委派是個問題。 發生這種情況時,記憶體流失會產生,因為已註冊的委派會讓原始物件保持運作。 因此,只有在您知道事件委派快速處置時,才使用下列方法。 當不確定需要處置的物件存留期時,請訂閱委派方法,並如先前範例所示正確地處置委派。

  • 匿名 Lambda 方法 (明確處置不需要) :

    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);
    }
    
  • 匿名 Lambda 運算式方法 (明確處置不需要) :

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

    具有匿名 Lambda 運算式的上述程式碼完整範例會出現在ASP.NET Core Blazor 表單和驗證一文中。

如需詳細資訊,請參閱 清除 Unmanaged 資源 及其後續 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 指引