共用方式為


ASP.NET Core Blazor 預先呈現的狀態持續性

若未保存元件狀態,在預先呈現期間所使用的狀態會遺失,而且必須在應用程式完全載入時重新建立。 如果任何狀態是非同步建立的,當元件重新渲染時,UI 可能會閃爍,因為預渲染的 UI 被取代。

請考慮以下 PrerenderedCounter1 計數器元件。 元件會在OnInitialized生命週期方法的預呈現階段設定初始隨機計數器值。 當元件接著以互動方式轉譯時,第二次執行時 OnInitialized 會取代初始計數值。

PrerenderedCounter1.razor

@page "/prerendered-counter-1"
@inject ILogger<PrerenderedCounter1> Logger

<PageTitle>Prerendered Counter 1</PageTitle>

<h1>Prerendered Counter 1</h1>

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

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

@code {
    private int currentCount;

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

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

備註

如果應用程式採用 互動式路由 ,且頁面是透過內部 增強式導覽來連線,則不會發生預先呈現。 因此,您必須重新載入整個頁面,才能查看PrerenderedCounter1元件的以下輸出。 如需詳細資訊,請參閱 互動式路由和預先呈現 一節。

啟動應用程式並檢查元件的日誌記錄。 以下是範例輸出。

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

第一次記錄的計數發生在預渲染過程中。 當組件重新渲染後,計數會在預渲染後重新設置。 UI 在計數從 41 更新到 92 時也會閃爍。

為了在預渲染期間保留計數器的初始值,Blazor 支援使用 PersistentComponentState 服務在預渲染的頁面中保持狀態(對於嵌入到 Razor 頁面或 MVC 應用程式的頁面或視圖中的元件,使用 Persist Component State Tag Helper)。

通過使用與預渲染時相同的狀態來初始化組件,可以確保任何高成本的初始化步驟只執行一次。 轉譯的 UI 也會符合預先轉譯的 UI,因此瀏覽器中不會發生閃爍。

持久化的預渲染狀態會被傳送到客戶端,用於恢復組件狀態。 在客戶端渲染(CSR, InteractiveWebAssembly)時,資料會暴露給瀏覽器,因此不得包含敏感和私人信息。 在互動式伺服器端渲染(interactive SSR,InteractiveServer)過程中,ASP.NET Core Data Protection確保資料的安全傳輸。 InteractiveAuto 渲染模式結合了 WebAssembly 和伺服器的互動性,因此有必要考慮數據暴露給瀏覽器的情況,就像在 CSR 情況一樣。

若要保留預先轉譯的狀態,請使用 [PersistentState] 屬性 將狀態保存在屬性中。 具有此屬性的屬性會在預先呈現期間使用 PersistentComponentState 服務自動保存。 當元件以互動方式轉譯或服務具現化時,就會擷取狀態。

根據預設,屬性會使用具有預設設定的 System.Text.Json 串行化程序進行串行化,並保存在預先呈現的 HTML 中。 串行化不是安全修剪器,而且需要保留所使用的類型。 如需詳細資訊,請參閱設定 ASP.NET Core Blazor 的修剪器

下列計數器元件會在預先呈現期間保存計數器狀態,並擷取狀態以初始化元件:

  • 屬性[PersistentState]會套用至可為 null 的int類型CurrentCount
  • 計數器的狀態會在 null 中被指定,當元件以互動方式渲染時會自動還原。

PrerenderedCounter2.razor

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

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

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

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

@code {
    [PersistentState]
    public int? CurrentCount { get; set; }

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

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

當元件執行時,CurrentCount 只在預渲染期間設定一次。 當元件重新渲染時,值將被恢復。 以下是範例輸出。

備註

如果應用程式採用 互動式路由 ,且頁面是透過內部 增強式導覽來連線,則不會發生預先呈現。 因此,您必須針對元件執行完整頁面重載,才能看到下列輸出。 如需詳細資訊,請參閱 互動式路由和預先呈現 一節。

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

在下列範例中,串行化相同類型的多個元件的狀態:

PersistentChild.razor

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

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

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

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

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

Parent.razor

@page "/parent"

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

服務狀態序列化

在下列範例中,用於序列化相依性注入服務的狀態:

  • 使用[PersistentState]屬性註解的屬性會在預先轉譯期間被序列化,並在應用程式變得互動時被反序列化。
  • RegisterPersistentService 擴充方法可用來註冊服務以進行持續。 因為無法從服務類型推斷轉譯模式,因此需要轉譯模式。 使用下列任何值:
    • RenderMode.Server:服務適用於互動式伺服器轉譯模式。
    • RenderMode.Webassembly:此服務適用於互動式 Webassembly 轉譯模式。
    • RenderMode.InteractiveAuto:如果元件在任一模式中轉譯,則服務適用於互動式伺服器和互動式Web元件轉譯模式。
  • 服務會在互動式渲染模式初始化期間被解析,並具備[PersistentState]屬性的屬性會被反序列化。

備註

支援的僅限於可持續的範圍服務。

從實際的服務實例識別串行化屬性:

  • 此方法允許將抽象概念標示為持續性服務。
  • 可讓實際實作成為內部或不同類型的實作。
  • 支援不同組件中的共享程式碼。
  • 導致每個實例顯示相同的屬性。

下列計數器服務 CounterTracker,將目前的計數屬性CurrentCount標記為[PersistentState]屬性。 屬性會在預先轉譯期間序列化,並在應用程式變成互動式且服務被插入的地方還原序列化。

CounterTracker.cs

public class CounterTracker
{
    [PersistentState]
    public int CurrentCount { get; set; }

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

Program檔案中,註冊範圍服務,而使用RegisterPersistentService註冊服務以進行持久性。 在下列範例中,服務CounterTracker可用於互動式伺服器和互動式 WebAssembly 渲染模式,只要元件在這些模式之一中渲染,因為它已註冊在RenderMode.InteractiveAuto中。

如果檔案尚未使用Program命名空間,請將下列Microsoft.AspNetCore.Components.Web陳述式新增至檔案的頂端。

using Microsoft.AspNetCore.Components.Web;

服務註冊在檔案 Program 的位置:

builder.Services.AddScoped<CounterTracker>();

builder.Services.AddRazorComponents()
    .RegisterPersistentService<CounterTracker>(RenderMode.InteractiveAuto);

CounterTracker 服務插入至元件中,使用它來增加計數器。 為了在下列範例中進行示範,服務屬性 CurrentCount 的值只會在預先轉譯期間設定為 10。

Pages/Counter.razor

@page "/counter"
@inject CounterTracker CounterTracker

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p>Rendering: @RendererInfo.Name</p>

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

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

@code {
    protected override void OnInitialized()
    {
        if (!RendererInfo.IsInteractive)
        {
            CounterTracker.CurrentCount = 10;
        }
    }

    private void IncrementCount()
    {
        CounterTracker.IncrementCount();
    }
}

若要使用上述元件來示範如何保存 中的 10 CounterTracker.CurrentCount計數,導覽至元件並重新整理瀏覽器,這會觸發預先轉譯。 發生預渲染時,您會先短暫看到 RendererInfo.Name 指示「Static」,然後在最終渲染後顯示「Server」。 計數器從10開始。

直接使用 PersistentComponentState 服務,而不是宣告式模型

作為使用宣告式模型透過[PersistentState]來保存狀態的替代方案,您可以直接使用服務,這在複雜的狀態保存方案中提供了更大的靈活性。 呼叫 PersistentComponentState.RegisterOnPersisting 以註冊回呼,以在預呈現期間保持元件狀態。 當元件以互動方式呈現時,會擷取狀態。 在初始化程式代碼結束時進行呼叫,以避免在應用程式關機期間發生潛在的競爭狀況。

以下的計數器組件範例會在預渲染期間持續保存計數器狀態,並檢索狀態以初始化元件。

PrerenderedCounter3.razor

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

<PageTitle>Prerendered Counter 3</PageTitle>

<h1>Prerendered Counter 3</h1>

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

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

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

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

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

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

        return Task.CompletedTask;
    }

    private void IncrementCount() => currentCount++;

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

當元件執行時,currentCount 只在預渲染期間設定一次。 當元件重新渲染時,值將被恢復。 以下是範例輸出。

備註

如果應用程式採用 互動式路由 ,且頁面是透過內部 增強式導覽來連線,則不會發生預先呈現。 因此,您必須針對元件執行完整頁面重載,才能看到下列輸出。 如需詳細資訊,請參閱 互動式路由和預先呈現 一節。

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

若要保留預先呈現的狀態,請決定要使用 PersistentComponentState 服務保存哪些狀態。 PersistentComponentState.RegisterOnPersisting 註冊一個回呼函數,以在預先呈現期間持久化元件狀態。 當元件以互動方式呈現時,會擷取狀態。 在初始化程式代碼結束時進行呼叫,以避免在應用程式關機期間發生潛在的競爭狀況。

以下的計數器組件範例會在預渲染期間持續保存計數器狀態,並檢索狀態以初始化元件。

PrerenderedCounter2.razor

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

<PageTitle>Prerendered Counter 2</PageTitle>

<h1>Prerendered Counter 2</h1>

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

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

@code {
    private int currentCount;
    private PersistingComponentStateSubscription persistingSubscription;

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

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

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

        return Task.CompletedTask;
    }

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

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

當元件執行時,currentCount 只在預渲染期間設定一次。 當元件重新渲染時,值將被恢復。 以下是範例輸出。

備註

如果應用程式採用 互動式路由 ,且頁面是透過內部 增強式導覽來連線,則不會發生預先呈現。 因此,您必須針對元件執行完整頁面重載,才能看到下列輸出。 如需詳細資訊,請參閱 互動式路由和預先呈現 一節。

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

持續性元件狀態的序列化擴充性

實作一個自訂序列化器。PersistentComponentStateSerializer<T> 如果沒有已註冊的自訂序列化程式,序列化會回復至現有的 JSON 序列化。

自訂序列化程式會註冊在應用程式的 Program 檔案中。 在下列範例中,會針對類型註冊 CustomUserSerializerTUser

builder.Services.AddSingleton<PersistentComponentStateSerializer<TUser>, 
    CustomUserSerializer>();

類型會自動保存並使用自訂序列化程式還原:

[PersistentState] 
public User? CurrentUser { get; set; } = new();

嵌入頁面和視圖的元件(Razor 頁面/MVC)

為了將組件嵌入到 Razor Pages 或 MVC 應用程式的頁面或視圖中,您必須在應用程式佈局的閉合 標籤內部,使用 <persist-component-state /> HTML 標籤添加 </body>僅需要對Razor頁面和MVC應用程式進行此操作。 如需更多資訊,請參閱ASP.NET Core 中的 Persist Component State Tag Helper

Pages/Shared/_Layout.cshtml

<body>
    ...

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

互動式路由和預渲染

Routes 元件未定義渲染模式時,應用程式會使用逐頁/元件互動和導航。 使用每頁/元件導覽時,內部流覽會在應用程式變成互動式之後,透過 增強式路由 來處理。 此內容中的「內部流覽」表示導覽事件的URL目的地是 Blazor 應用程式內的端點。

Blazor 支援在 增強型導覽期間處理持續性元件狀態。 在增強型導覽期間保存的狀態可以由頁面上的互動式元件讀取。

根據預設,持續性元件狀態只會在互動式元件最初載入頁面時載入。 這可防止重要狀態 (例如已編輯的 Web 表單中的資料) 在載入元件後發生相同頁面的其他增強型導覽事件時被覆寫。

如果資料是唯讀且不經常變更,請透過設定AllowUpdates = true[PersistentState]屬性來選擇允許在增強型導覽期間進行更新。 這適用於顯示擷取成本高昂但不經常變更的快取資料等案例。 下列範例示範 AllowUpdates 如何用於天氣預報資料:

[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await ForecastService.GetForecastAsync();
}

若要在預先轉譯期間略過還原狀態,請設定 RestoreBehaviorSkipInitialValue

[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string NoPrerenderedData { get; set; }

若要在重新連線期間略過還原狀態,請設定 RestoreBehaviorSkipLastSnapshot。 這對於確保重新連線後的資料更新非常有用:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public int CounterNotRestoredOnReconnect { get; set; }

呼叫 PersistentComponentState.RegisterOnRestoring 以註冊回呼,以命令式控制狀態的還原方式,類似於提供狀態保存方式的完整控制方式 PersistentComponentState.RegisterOnPersisting

PersistentComponentState 服務只能在初始頁面載入上運作,而不適用於內部增強式頁面流覽事件。

如果應用程式執行完整(非增強型)導航至使用持久性元件狀態的頁面,當應用程式開始具有互動性時,該持久狀態將可供使用。

如果已建立互動式電路,並且在執行增強的導航時使用持久性元件狀態導航到一個頁面,則該狀態在現有電路中不會提供給元件使用。 內部頁面要求沒有預渲染,且 PersistentComponentState 服務不知道已發生增強式導覽。 沒有機制可傳遞狀態更新至已在現有線路上執行的元件。 原因是 Blazor 只支援在運行時間初始化時將狀態從伺服器傳遞至用戶端,而不是在運行時間啟動之後。

關閉增強的導覽功能會降低效能,但也避免了內部頁面請求時載入狀態出現 PersistentComponentState 問題,這一點在 ASP.NET Core Blazor 導覽中有提及。 或者,將應用程式更新為 .NET 10 或更新版本,其中 Blazor 支援在增強型導覽期間處理持續性元件狀態。