protected override async Task OnInitializedAsync()
await ...
await base.OnInitializedAsync();
除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnInitializedAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。
元件必須確保當OnInitializedAsync等待可能尚未完成的Task時,處於有效的呈現狀態。 如果方法傳回不完整 Task,則同步完成之方法的一部分必須讓元件處於有效的轉譯狀態。 如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容 和 ASP.NET Core Razor 元件處置的簡介備註。
在伺服器上預先轉譯其內容的 Blazor 應用程式會呼叫 OnInitializedAsync兩次:
元件最初被靜態渲染作為頁面的一部分時。
第二次是在瀏覽器渲染元件時。
若要防止 OnInitializedAsync 中的開發人員程式碼在預先轉譯時執行兩次,請參閱預先轉譯之後的具狀態重新連線一節。 本節內容著重於 Blazor Web App和具狀態的SignalR重新連線。 若要在預先轉譯初始化程式碼執行期間保留狀態,請參閱預先轉譯 ASP.NET CoreRazor 元件。
若要防止 OnInitializedAsync 中的開發人員程式碼在預先轉譯時執行兩次,請參閱預先轉譯之後的具狀態重新連線一節。 雖然本節內容著重於 Blazor Server 和具狀態的SignalR重新連線,但在托管 Blazor WebAssembly 方案(WebAssemblyPrerendered) 中的預渲染場景涉及類似的條件和方法,以防止開發人員程式碼被執行兩次。 若要在預先呈現期間保留初始化程式代碼的狀態,請參閱 將 ASP.NET Core Razor 元件整合到 MVC 或 Razor 頁面中。
雖然 Blazor 應用程式正在預先渲染,但無法進行如呼叫 JavaScript (JS 互操作) 等特定動作。 預先轉譯時,元件可能需要以不同的方式轉譯。 如需詳細資訊,請參閱使用 JavaScript Interop 預先轉譯一節。
如果在開發人員程式碼中提供事件處理常式,請在處置時解除綁定。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置。
使用串流轉譯搭配靜態伺服器端轉譯 (靜態 SSR) 或預先轉譯,以改善在 OnInitializedAsync 中執行長時間執行非同步工作之元件的使用者體驗,以便完全轉譯。 如需詳細資訊,請參閱以下資源:
處理渲染時的不完整異步操作 (本文)
ASP.NET Core Razor 元件渲染
稱為 OnParametersSet 或 OnParametersSetAsync:
元件在OnInitialized或OnInitializedAsync中初始化後。
當父元件重新渲染並提供時:
至少有一個參數已改變時的已知或原始不可變類型。
複雜型別參數。 架構無法得知複雜型別參數的值是否已在內部變動,因此當一或多個複雜型別參數存在時,架構一律會將參數集視為已變更。
如需關於轉譯慣例的詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯。
同步方法是在非同步方法之前呼叫。
即使參數值尚未變更,也可以叫用方法。 這種行為強調開發人員需要在方法內實作其他邏輯,以檢查參數值是否確實在重新初始化資料或相依於這些參數的狀態之前已變更。
針對下列範例元件,導航至在 URL 中的元件的頁面:
在 StartDate
收到的開始日期為:/on-parameters-set/2021-03-19
沒有開始日期,此情況下 StartDate
會指派目前當地時間的值:/on-parameters-set
在元件路由中,不可能同時使用DateTime參數的路由限制datetime
,並將參數設為選擇性。 因此,下列 OnParamsSet
元件會使用兩個 @page
指示詞,來處理 URL 中提供與未提供日期區段的路由。
OnParamsSet.razor
:
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
<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}).";
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<PageTitle>On Parameters Set</PageTitle>
<h1>On Parameters Set Example</h1>
Pass a datetime in the URI of the browser's address bar.
For example, add <code>/1-1-2024</code> to the address.
<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}).";
message = $"The start date in the URL was used " +
$"(StartDate: {StartDate}).";
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
if (StartDate == default)
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
message = $"The start date in the URL was used (StartDate: {StartDate}).";
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string? message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
if (StartDate == default)
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
message = $"The start date in the URL was used (StartDate: {StartDate}).";
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
if (StartDate == default)
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
message = $"The start date in the URL was used (StartDate: {StartDate}).";
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"
<p>@message</p>
@code {
private string message;
[Parameter]
public DateTime StartDate { get; set; }
protected override void OnParametersSet()
if (StartDate == default)
StartDate = DateTime.Now;
message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
message = $"The start date in the URL was used (StartDate: {StartDate}).";
套用參數和屬性值時,必須在 OnParametersSetAsync 生命週期事件期間進行非同步工作:
protected override async Task OnParametersSetAsync()
await ...
如果自訂基底類別搭配自訂初始化邏輯使用,請在基底類別上呼叫 OnParametersSetAsync:
protected override async Task OnParametersSetAsync()
await ...
await base.OnParametersSetAsync();
除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnParametersSetAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。
元件必須確保當OnParametersSetAsync等待可能尚未完成的Task時,處於有效的呈現狀態。 如果方法傳回不完整 Task,則同步完成之方法的一部分必須讓元件處於有效的轉譯狀態。 如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容 和 ASP.NET Core Razor 元件處置的簡介備註。
如果在開發人員程式碼中提供事件處理常式,請在處置時解除綁定。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置。
如果可處置元件未使用 CancellationToken,那麼 OnParametersSet 和 OnParametersSetAsync 應檢查該元件是否已被處置。 如果 OnParametersSetAsync 傳回不完整 Task,元件必須確定同步完成的方法部分會讓元件處於有效的轉譯狀態。 如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容 和 ASP.NET Core Razor 元件處置的簡介備註。
如需路由參數和限制式的詳細資訊,請參閱 ASP.NET Core Blazor 路由和導覽。
如需在某些案例中手動實作 SetParametersAsync
以改善效能的範例,請參閱 ASP.NET Core Blazor 轉譯效能最佳做法。
元件渲染後 (OnAfterRender{Async}
)
OnAfterRender 和 OnAfterRenderAsync 會在元件以互動方式轉譯且 UI 完成更新之後叫用 (例如,將元素新增至瀏覽器 DOM 之後)。 元素和元件的參考在此時被填入。 使用此階段來執行轉譯內容的其他初始化步驟,例如與轉譯 DOM 元素互動的 JS Interop 呼叫。 同步方法是在非同步方法之前呼叫。
這些方法不會在伺服器上於預先轉譯或靜態伺服器端轉譯 (靜態 SSR) 期間叫用,因為這些流程不會附加至即時瀏覽器 DOM,且已在更新 DOM 之前完成。
針對 OnAfterRenderAsync,元件不會在任何傳回的 Task
完成之後自動重新轉譯,以避免無限轉譯迴圈。
OnAfterRender 和 OnAfterRenderAsync 會在元件完成轉譯之後呼叫。 元素和元件的參考在此時被填入。 使用此階段來執行轉譯內容的其他初始化步驟,例如與轉譯 DOM 元素互動的 JS Interop 呼叫。 同步方法是在非同步方法之前呼叫。
這些方法不會在預先定義期間叫用,因為預先轉譯不會附加至即時瀏覽器 DOM,而且已在更新 DOM 之前完成。
針對 OnAfterRenderAsync,元件不會在任何傳回的 Task
完成之後自動重新轉譯,以避免無限轉譯迴圈。
firstRender
和 OnAfterRender 的 OnAfterRenderAsync 參數:
首次渲染元件實例時,會將其設定為 true
。
可以用來確保初始化工作只會執行一次。
AfterRender.razor
:
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender) =>
Logger.LogInformation("firstRender = {FirstRender}", firstRender);
private void HandleClick() => Logger.LogInformation("HandleClick called");
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
private void HandleClick()
Logger.LogInformation("HandleClick called");
@page "/after-render"
@inject ILogger<AfterRender> Logger
<PageTitle>After Render</PageTitle>
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
private void HandleClick()
Logger.LogInformation("HandleClick called");
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
private void HandleClick()
Logger.LogInformation("HandleClick called");
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger
<h1>After Render Example</h1>
<button @onclick="HandleClick">Log information (and trigger a render)</button>
<p>Study logged messages in the console.</p>
@code {
protected override void OnAfterRender(bool firstRender)
Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
private void HandleClick()
Logger.LogInformation("HandleClick called");
載入頁面並選取按鈕時,AfterRender.razor
範例會產生下列輸出至主控台:
OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False
在轉譯之後立即進行的非同步工作,必須在 OnAfterRenderAsync 生命週期事件期間發生:
protected override async Task OnAfterRenderAsync(bool firstRender)
如果自訂基底類別搭配自訂初始化邏輯使用,請在基底類別上呼叫 OnAfterRenderAsync:
protected override async Task OnAfterRenderAsync(bool firstRender)
await base.OnAfterRenderAsync(firstRender);
除非自訂基底類別搭配自訂邏輯使用,否則不需要呼叫 ComponentBase.OnAfterRenderAsync。 如需詳細資訊,請參閱基底類別生命週期方法一節。
即使您從 Task 返回 OnAfterRenderAsync,架構在完成該任務後也不會為元件安排進一步的渲染周期。 這是為了避免無限轉譯迴圈。 這與其他生命週期方法不同,其他生命週期方法會在傳回的 Task 完成之後排程進一步的渲染週期。
OnAfterRender 和 OnAfterRenderAsync在伺服器的預渲染過程中不會被呼叫。 當元件在預先呈現之後以互動方式呈現時,會呼叫這些方法。 當應用程式預渲染時:
元件會在伺服器上執行,以在 HTTP 回應中產生一些靜態 HTML 標記。 在此階段期間,不會呼叫 OnAfterRender 和 OnAfterRenderAsync。
當 Blazor 指令碼 (blazor.{server|webassembly|web}.js
) 在瀏覽器中啟動時,元件會以互動式轉譯模式重新啟動。 重新啟動元件之後,「會」呼叫 OnAfterRender 和 OnAfterRenderAsync,因為應用程式不再處於預先轉譯階段。
如果在開發人員程式碼中提供事件處理常式,請在處置時解除綁定。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件處置。
基底類別生命週期方法
覆寫 Blazor 的生命週期方法時,不需要呼叫 ComponentBase 的基底類別生命週期方法。 不過,在下列情況下,元件應該呼叫已覆寫的基底類別生命周期方法:
覆寫 ComponentBase.SetParametersAsync 時,通常會叫用 await base.SetParametersAsync(parameters);
,因為基底類別方法會呼叫其他生命週期方法,並以複雜的方式觸發轉譯。 如需詳細資訊,請參閱何時設定參數 (SetParametersAsync
) 一節。
如果基底類別方法包含必須執行的邏輯。 程式庫取用者通常會在繼承基底類別時呼叫基底類別生命週期方法,因為程式庫基底類別通常有要執行的自訂生命週期邏輯。 如果應用程式使用程式庫中的基底類別,請參閱程式庫的文件以取得指引。
在下列範例中,會呼叫 base.OnInitialized();
,以確保執行基底類別的 OnInitialized
方法。 若沒有呼叫,BlazorRocksBase2.OnInitialized
就不會執行。
BlazorRocks2.razor
:
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<PageTitle>Blazor Rocks!</PageTitle>
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger
<h1>Blazor Rocks! Example 2</h1>
@BlazorRocksText
@code {
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocks2 executed!");
base.OnInitialized();
BlazorRocksBase2.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";
protected override void OnInitialized() =>
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
using Microsoft.AspNetCore.Components;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
namespace BlazorSample;
public class BlazorRocksBase2 : ComponentBase
[Inject]
private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;
public string BlazorRocksText { get; set; } =
"Blazor rocks the browser!";
protected override void OnInitialized()
Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
狀態變更 (StateHasChanged
)
StateHasChanged 通知元件其狀態已變更。 適用時,調用 StateHasChanged 可在應用程式的主要執行緒空閒時將重新渲染加入佇列。
系統會自動針對 StateHasChanged 方法呼叫 EventCallback。 如需事件回撥的詳細資訊,請參閱 ASP.NET CoreBlazor 事件處理。
如需關於元件轉譯和何時呼叫 StateHasChanged (包括何時使用 ComponentBase.InvokeAsync 叫用) 的詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯 。
處理渲染時不完整的異步操作
在生命週期事件中執行的非同步動作,在轉譯元件前可能尚未完成。 當生命週期方法執行時,物件可能會 null
或不完整地填入資料。 提供轉譯邏輯,以確認物件已初始化。 顯示預留位置的 UI 元素(例如載入訊息),當物件為null
時。
在下列 Slow
元件中,OnInitializedAsync 被覆蓋以非同步方式執行長時間運行的工作。 當 isLoading
是 true
時,載入訊息會顯示給使用者。
Task
傳回的 OnInitializedAsync 完成之後,元件會以更新的狀態重新呈現,並顯示 「Finished!
」 訊息。
Slow.razor
:
@page "/slow"
<h2>Slow Component</h2>
@if (isLoading)
<div><em>Loading...</em></div>
<div>Finished!</div>
@code {
private bool isLoading = true;
protected override async Task OnInitializedAsync()
await LoadDataAsync();
isLoading = false;
private Task LoadDataAsync()
return Task.Delay(10000);
上述元件會使用 isLoading
變數來顯示載入訊息。 類似的方法用於將數據載入集合的元件,並檢查集合是否 null
來呈現載入訊息。 下列範例會檢查 movies
集合中的 null
,以決定顯示載入訊息或顯示電影集合:
@if (movies == null)
<p><em>Loading...</em></p>
@* display movies *@
@code {
private Movies[]? movies;
protected override async Task OnInitializedAsync()
movies = await GetMovies();
預先呈現會等候 靜止,這表示在渲染樹狀結構中的所有元件都完成渲染之前,該元件不會渲染。 這表示在子元件的 OnInitializedAsync 方法在預先呈現期間執行長時間工作的時候,不會顯示載入訊息。 若要示範此行為,請將上述 Slow
元件放入測試應用程式的 Home
元件:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Slow />
雖然本節中的範例討論 OnInitializedAsync 生命週期方法,但在預先呈現期間執行的其他生命週期方法可能會延遲元件的最終轉譯。 在預先呈現期間,OnAfterRender{Async}
不會執行,並且不會因靜止狀態而受到延遲影響。
在預先呈現期間,Home
元件要等到 Slow
元件完成轉譯後才會呈現,而轉譯 Slow
元件需要十秒鐘。 UI 在這個十秒期間是空白的,而且沒有載入訊息。 預渲染之後,Home
元件會渲染,並顯示 Slow
元件的載入訊息。 超過十秒之後,Slow
元件最後會顯示已完成的訊息。
如上述示範所示,在預先呈現期間靜止會導致用戶體驗不佳。 若要改善用戶體驗,請從實施 串流轉譯 開始,以避免在預渲染的時候等待異步工作完成。
將 [StreamRendering]
屬性 新增至 Slow
元件(在 .NET 8 中使用 [StreamRendering(true)]
):
@attribute [StreamRendering]
當 Home
元件預先呈現時,Slow
元件會快速渲染並顯示載入訊息。
Home
元件不會等候 10 秒,讓 Slow
元件完成轉譯。 不過,當預先呈現結束時,顯示的完成訊息會被載入訊息取代,然後元件開始最終渲染,這又造成了十秒鐘的延遲。 這是因為 Slow
元件會渲染兩次,LoadDataAsync
也會執行兩次。 當元件存取服務與資料庫等資源時,服務呼叫和資料庫查詢的雙重執行會在應用程式的資源上建立不想要的負載。
若要解決載入訊息被重複呈現以及服務和資料庫呼叫的重複執行問題,請將預先呈現的狀態保存到 PersistentComponentState,以供元件的最終渲染,如下列 Slow
元件的更新所示:
@page "/slow"
@attribute [StreamRendering]
<h2>Slow Component</h2>
@if (Data is null)
<div><em>Loading...</em></div>
<div>@Data</div>
@code {
[SupplyParameterFromPersistentComponentState]
public string? Data { get; set; }
protected override async Task OnInitializedAsync()
Data ??= await LoadDataAsync();
private async Task<string> LoadDataAsync()
await Task.Delay(5000);
return "Finished!";
@page "/slow"
@attribute [StreamRendering]
@implements IDisposable
@inject PersistentComponentState ApplicationState
<h2>Slow Component</h2>
@if (data is null)
<div><em>Loading...</em></div>
<div>@data</div>
@code {
private string? data;
private PersistingComponentStateSubscription persistingSubscription;
protected override async Task OnInitializedAsync()
if (!ApplicationState.TryTakeFromJson<string>(nameof(data), out var restored))
data = await LoadDataAsync();
data = restored!;
// Call at the end to avoid a potential race condition at app shutdown
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
private Task PersistData()
ApplicationState.PersistAsJson(nameof(data), data);
return Task.CompletedTask;
private async Task<string> LoadDataAsync()
await Task.Delay(5000);
return "Finished!";
void IDisposable.Dispose()
persistingSubscription.Dispose();
透過結合串流渲染與持續性元件狀態:
服務和資料庫只需要單一呼叫元件初始化。
元件會在長時間執行的工作期間快速渲染其UI,並顯示載入訊息,以獲得最佳用戶體驗。
如需詳細資訊,請參閱以下資源:
ASP.NET Core Razor 元件渲染
預渲染 ASP.NET Core Razor 元件。
moniker-end
預先呈現期間的靜止會導致用戶體驗不佳。 延遲問題可以在以 .NET 8 或更新版本為目標的應用程式中透過名為 串流渲染的功能解決,通常會結合 在預渲染期間保存元件狀態 以避免等待異步任務完成。 在.NET 8 之前的 .NET 版本中,執行長時間背景工作,在最終渲染之後載入資料,可能會解決因靜止狀態導致的長時間渲染延遲問題。
如需關於在生命週期方法執行期間處理錯誤的資訊,請參閱處理 ASP.NET Core Blazor 應用程式中的錯誤。
預渲染後的狀態化重新連線
在伺服器上預先轉譯時,元件一開始會以靜態方式轉譯為頁面的一部分。 一旦瀏覽器與伺服器建立了SignalR連線,元件就會再次被渲染並提供互動功能。 如果元件初始化的 OnInitialized{Async}
生命週期方法存在,則會執行方法兩次:
當元件以靜態方式預先渲染時。
在伺服器連線建立之後。
這可能會導致最終轉譯元件時,UI 中顯示的資料有明顯變更。 若要避免此行為,請在預渲染期間傳入識別碼以快取狀態,並在預渲染之後擷取狀態。
下列程式碼示範了 WeatherForecastService
,以避免因預先渲染而改變資料顯示。 預期的 Delay (await Task.Delay(...)
) 會模擬一次短暫的延遲,然後再從 GetForecastAsync
方法返回數據。
在應用程式的 IMemoryCache 檔案中,將 AddMemoryCache 服務與 Program
新增到服務集合中。
builder.Services.AddMemoryCache();
WeatherForecastService.cs
:
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
private static readonly string[] summaries =
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
using Microsoft.Extensions.Caching.Memory;
namespace BlazorSample;
public class WeatherForecastService(IMemoryCache memoryCache)
private static readonly string[] summaries =
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public IMemoryCache MemoryCache { get; } = memoryCache;
public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
private static readonly string[] summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public WeatherForecastService(IMemoryCache memoryCache)
MemoryCache = memoryCache;
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
using Microsoft.Extensions.Caching.Memory;
public class WeatherForecastService
private static readonly string[] summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public WeatherForecastService(IMemoryCache memoryCache)
MemoryCache = memoryCache;
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
private static readonly string[] summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public WeatherForecastService(IMemoryCache memoryCache)
MemoryCache = memoryCache;
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;
public class WeatherForecastService
private static readonly string[] summaries = new[]
"Freezing", "Bracing", "Chilly", "Cool", "Mild",
"Warm", "Balmy", "Hot", "Sweltering", "Scorching"
public WeatherForecastService(IMemoryCache memoryCache)
MemoryCache = memoryCache;
public IMemoryCache MemoryCache { get; }
public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
return MemoryCache.GetOrCreateAsync(startDate, async e =>
e.SetOptions(new MemoryCacheEntryOptions
AbsoluteExpirationRelativeToNow =
TimeSpan.FromSeconds(30)
var rng = new Random();
await Task.Delay(TimeSpan.FromSeconds(10));
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
Date = startDate.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = summaries[rng.Next(summaries.Length)]
}).ToArray();
如需關於 RenderMode 的詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指導。
本節的內容集中於Blazor Web App和有狀態的SignalR重新連接。 若要在預先轉譯初始化程式碼執行期間保留狀態,請參閱預先轉譯 ASP.NET CoreRazor 元件。
雖然本節內容著重於 Blazor Server 和具狀態的SignalR重新連線,但在託管 Blazor WebAssembly 解決方案 (WebAssemblyPrerendered) 中進行預先轉譯的情景涉及相似的條件和方法,以防止開發人員程式碼被執行兩次。 若要在預先呈現期間保留初始化程式代碼的狀態,請參閱 將 ASP.NET Core Razor 元件整合到 MVC 或 Razor 頁面中。
使用 JavaScript Interop 預渲染
本節適用於會預先轉譯 Razor 元件的伺服器端應用程式。
Prerender ASP.NET Core Razor 元件中介紹了預先轉譯。
在中的Blazor Web App的內部導航不涉及向伺服器請求新頁面內容。 因此,內部頁面請求不會進行預渲染。 如果應用程式採用互動式路由,請針對示範預先轉譯行為的元件範例執行完整頁面重載。 如需詳細資訊,請參閱預先轉譯 ASP.NET Core Razor 元件。
本節適用於會預先轉譯 Blazor WebAssembly 元件的伺服器端應用程式和所裝載的 Razor 應用程式。 預先呈現涵蓋在 整合 ASP.NET Core Razor 元件與 MVC 或 Razor 頁面 中。
在預渲染期間,無法調用 JavaScript(JS)。 下列範例示範如何透過與預先轉譯相容的方式,將 JS Interop 作為元件初始化邏輯的一部分。
以下scrollElementIntoView
函數:
使用 scrollIntoView
捲動到指定的元素。
從 top
方法傳回元素的 getBoundingClientRect
屬性值。
window.scrollElementIntoView = (element) => {
element.scrollIntoView();
return element.getBoundingClientRect().top;
當 IJSRuntime.InvokeAsync 在元件程式碼呼叫 JS 函式時, ElementReference 僅在 OnAfterRenderAsync 使用,而不在任何先前的生命週期方法中使用,因為要等到元件轉譯後才會有 HTML DOM 元素。
呼叫 StateHasChanged
(參考來源) ,將使用從 JS interop 呼叫取得的新狀態將元件的重新渲染排入佇列 (如需詳細資訊,請參閱 ASP.NET Core Razor 元件渲染)。 此程式碼不會無限迴圈,因為只有在 StateHasChanged 是 scrollPosition
時才會呼叫 null
。
PrerenderedInterop.razor
:
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<PageTitle>Prerendered Interop</PageTitle>
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender && scrollPosition is null)
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender && scrollPosition is null)
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender && scrollPosition is null)
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender && scrollPosition is null)
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS
<h1>Prerendered Interop Example</h1>
<div @ref="divElement" style="margin-top:2000px">
Set value via JS interop call: <strong>@scrollPosition</strong>
@code {
private ElementReference divElement;
private double? scrollPosition;
protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender && scrollPosition is null)
scrollPosition = await JS.InvokeAsync<double>(
"scrollElementIntoView", divElement);
StateHasChanged();
上述範例會使用全域函式敗壞用戶端。 如需更適合生產應用程式的方法,請參閱 JavaScript 模組中的 JavaScript 隔離。
可取消的背景工作
元件通常會執行長時間執行的背景工作,例如進行網路呼叫 (HttpClient) 並與資料庫互動。 建議您在數種情況下停止背景工作,以節省系統資源。 例如,當使用者瀏覽離開元件時,背景非同步作業不會自動停止。
背景工作項目可能需要取消的其他原因包括:
執行中的背景工作是以錯誤輸入資料或處理參數啟動。
目前執行背景工作項目的集合必須取代為一組新工作項目。
目前執行中工作的優先順序必須變更。
應用程式必須關閉,才能重新部署伺服器。
伺服器資源變得有限,需要重新排程背景工作項目。
若要在元件中實作可取消的背景工作模式:
使用 CancellationTokenSource 和 CancellationToken。
在 處置元件時,若在任何時間點需要取消,請手動取消令牌,然後呼叫 CancellationTokenSource.Cancel
,以表示應該取消背景工作。
非同步呼叫完成後,請在憑證上呼叫 ThrowIfCancellationRequested。
在以下範例中:
await Task.Delay(10000, cts.Token);
代表長時間執行的非同步背景工作。
BackgroundResourceMethod
代表長時間執行的背景方法。如果在呼叫方法之前 Resource
已被處置,則不應啟動此方法。
BackgroundWork.razor
:
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
<p>Study logged messages in the console.</p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
cts.Cancel();
cts?.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose() => disposed = true;
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<PageTitle>Background Work</PageTitle>
<h1>Background Work Example</h1>
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
<p>Study logged messages in the console.</p>
If you trigger disposal within 10 seconds of page load, the
<code>BackgroundResourceMethod</code> isn't executed.
If disposal occurs after <code>BackgroundResourceMethod</code> is called but
before action is taken on the resource, an <code>ObjectDisposedException</code>
is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
processed.
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
private IList<string> messages = [];
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(10000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
if (!cts.IsCancellationRequested)
cts.Cancel();
cts?.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose() => disposed = true;
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose()
disposed = true;
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose()
disposed = true;
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new();
private CancellationTokenSource cts = new();
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose()
disposed = true;
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger
<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>
@code {
private Resource resource = new Resource();
private CancellationTokenSource cts = new CancellationTokenSource();
protected async Task LongRunningWork()
Logger.LogInformation("Long running work started");
await Task.Delay(5000, cts.Token);
cts.Token.ThrowIfCancellationRequested();
resource.BackgroundResourceMethod(Logger);
public void Dispose()
Logger.LogInformation("Executing Dispose");
cts.Cancel();
cts.Dispose();
resource?.Dispose();
private class Resource : IDisposable
private bool disposed;
public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
logger.LogInformation("BackgroundResourceMethod: Start method");
if (disposed)
logger.LogInformation("BackgroundResourceMethod: Disposed");
throw new ObjectDisposedException(nameof(Resource));
// Take action on the Resource
logger.LogInformation("BackgroundResourceMethod: Action on Resource");
public void Dispose()
disposed = true;
若要在背景工作進行時顯示載入指標,請使用下列方法。
使用帶有Loading
參數的載入指標元件,以便在RenderFragment中顯示子內容。 對於Loading
參數:
當 true
時,顯示載入指標。
當false
時,渲染元件的內容(ChildContent
)。 如需詳細資訊,請參閱 子內容轉譯片段。
ContentLoading.razor
:
@if (Loading)
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
@ChildContent
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
[Parameter]
public bool Loading { get; set; }
若要載入指標的 CSS 樣式,請將樣式新增至 <head>
元件的 HeadContent 內容。 如需更多資訊,請參閱ASP.NET Core Blazor 應用程式中的標題內容控制。
@if (Loading)
<!-- OPTIONAL ...
<HeadContent>
<style>
</style>
</HeadContent>
<progress id="loadingIndicator" aria-label="Content loading…"></progress>
@ChildContent
將此元件的 Razor 標記用 ContentLoading
元件包裝,並在元件執行初始化工作時,將 C# 欄位的值傳遞給 Loading
參數:
<ContentLoading Loading="@loading">
</ContentLoading>
@code {
private bool loading = true;
protected override async Task OnInitializedAsync()
await LongRunningWork().ContinueWith(_ => loading = false);
Blazor Server 重新連線事件
本文所涵蓋的元件生命週期事件會與伺服器端重新連線事件處理常式分開運作。 當與用戶端的 SignalR 連線遺失時,僅會中斷 UI 更新作業。 重新建立連線時,會繼續 UI 更新。 如需線路處理常式事件和設定的詳細資訊,請參閱 ASP.NET Core BlazorSignalR 指導。
在Razor元件的生命週期之外處理捕獲的例外