ASP.NET Core Blazor 狀態管理

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

本文說明在使用者使用應用程式和跨瀏覽器工作階段時,維護使用者資料 (狀態) 的常見方法。

注意

本文中的程式碼範例採用 可為 Null 的參考型別 (NRT) 和 .NET 編譯器 Null 狀態靜態分析,這在 .NET 6 或更新版本的 ASP.NET Core 中受到支援。 以 ASP.NET Core 5.0 或更早版本為目標時,請從本文範例的類型中移除 Null 類型指定 (?)。

維護使用者狀態

伺服器端 Blazor 是具狀態的應用程式架構。 大部分時候,應用程式會維護與伺服器的連線。 使用者的狀態會保留在線路的伺服器記憶體中。

保留在線路中的使用者狀態範例包括:

  • 元件執行個體的階層及其轉譯 UI 中最新的轉譯輸出。
  • 元件執行個體中的欄位和屬性值。
  • 資料存放在相依性插入 (DI) 服務執行個體中,且範圍限定於線路。

您也可以透過 JavaScript Interop 呼叫,在瀏覽器記憶體集的 JavaScript 變數中找到使用者狀態。

如果使用者遇到暫時的網路連線遺失,Blazor 會嘗試以原始狀態將使用者重新連線到其原始線路。 不過,將使用者重新連線到其在伺服器記憶體中的原始線路並不一定可行:

  • 伺服器無法永遠保留中斷連線的線路。 伺服器在逾時或伺服器承受記憶體壓力時,必須釋放中斷連線的線路。
  • 在多伺服器、負載平衡的部署環境中,當不再需要處理整體要求量時,個別伺服器可能會失敗或自動移除。 當使用者嘗試重新連線時,使用者的原始伺服器處理要求可能會變成無法使用。
  • 使用者可能會關閉並重新開啟其瀏覽器或重新載入頁面,這會移除瀏覽器記憶體中保留的任何狀態。 例如,透過 JavaScript Interop 呼叫設定的 JavaScript 變數值會遺失。

當使用者無法重新連線到其原始線路時,使用者會收到具有空白狀態的新線路。 這相當於關閉並重新開啟傳統型應用程式。

保存跨線路狀態

一般而言,會在使用者主動建立資料的線路之間維護狀態,而不只是讀取已經存在的資料。

若要跨線路保留狀態,應用程式必須將資料保存到伺服器記憶體以外的其他儲存位置。 狀態持續性並非自動。 開發應用程式以實作具狀態資料持續性時,您必須採取一些步驟。

通常只有使用者花費大量精力建立的高價值狀態才需要資料持續性。 在下列範例中,保存狀態可節省時間,或協助商務工作:

  • 多步驟 Web 表單:如果使用者的狀態遺失,則重新輸入多步驟 Web 表單數個已完成步驟的資料,對於使用者會相當耗時。 如果使用者離開表單並在稍後返回,則在此案例中就會失去狀態。
  • 購物車:可以維護代表潛在營收應用程式的任何商業重要元件。 失去狀態的使用者連同他們的購物車,在稍後返回網站時,可能會購買較少的產品或服務。

應用程式只能保存應用程式狀態。 無法保存 UI,例如元件執行個體及其轉譯樹狀結構。 元件和轉譯樹狀結構通常無法序列化。 若要保存 UI 狀態 (例如樹狀檢視控制項的展開節點),應用程式必須使用自訂程式碼,將 UI 狀態的行為模型化為可序列化的應用程式狀態。

保存狀態的位置

保存狀態的常見位置存在:

伺服器端儲存體

針對跨越多個使用者和裝置的永久資料持續性,應用程式可以使用伺服器端儲存體。 這些選項包括:

  • Blob 儲存體
  • 金鑰值儲存體
  • 關聯式資料庫
  • 表格儲存體

儲存資料之後,會保留使用者的狀態,並可在任何新的線路中使用。

如需 Azure 資料儲存體選項的詳細資訊,請參閱下列各項:

URL

若為代表瀏覽狀態的暫時性資料,請將資料模型化為 URL 的一部分。 在 URL 中建立模型的使用者狀態範例包括:

  • 已檢視實體的識別碼。
  • 分頁方格中的目前頁碼。

會保留瀏覽器網址列的內容:

  • 如果使用者手動重新載入頁面。
  • 如果網頁伺服器無法使用,且使用者被迫重新載入頁面,才能連線到不同的伺服器。

如需使用 @page 指示詞定義 URL 模式的資訊,請參閱 ASP.NET Core Blazor 路由和導覽

瀏覽器儲存體

針對使用者正在主動建立的暫時性資料,常用的儲存位置是瀏覽器的 localStoragesessionStorage 集合:

  • localStorage 範圍設定為瀏覽器的視窗。 如果使用者重新載入頁面或關閉並重新開啟瀏覽器,會保存狀態。 如果使用者開啟多個瀏覽器索引標籤,狀態會跨索引標籤共用。 資料會持續存在 localStorage 中,直到明確清除為止。
  • sessionStorage 範圍設定為瀏覽器索引標籤。如果使用者重新載入索引標籤,則狀態會持續。 如果使用者關閉索引標籤或瀏覽器,狀態就會遺失。 如果使用者開啟多個瀏覽器索引標籤,則每個索引標籤都有自己的獨立資料版本。

一般而言,sessionStorage 使用上更安全。 sessionStorage 可避免使用者開啟多個索引標籤並遇到下列風險:

  • 跨索引標籤在狀態儲存體中的錯誤。
  • 當索引標籤覆寫其他索引標籤的狀態時,造成混淆的行為。

如果應用程式必須在關閉並重新開啟瀏覽器時保存狀態,則 localStorage 為較佳的選擇。

使用瀏覽器儲存體的注意事項:

  • 類似於使用伺服器端資料庫,載入及儲存資料為非同步。
  • 不同於伺服器端資料庫,在預先呈現期間無法使用儲存體,因為在預先轉譯階段期間,要求的頁面不存在於瀏覽器中。
  • 對於伺服器端 Blazor 應用程式,儲存幾 KB 的資料是合理的。 除了幾 KB 之外,您還必須考量效能影響,因為資料會透過網路載入並儲存。
  • 使用者可以檢視或竄改資料。 ASP.NET Core Data Protection 可以降低風險。 例如,ASP.NET Core Protected Browser Storage 會使用 ASP.NET Core Data Protection。

協力廠商 NuGet 套件提供使用 localStoragesessionStorage 的 API。 建議您考慮選擇以透明方式使用 ASP.NET Core Data Protection 的套件。 資料保護會加密儲存的資料,並降低竄改已儲存資料的潛在風險。 如果 JSON 序列化資料以純文字儲存,則使用者可以使用瀏覽器開發人員工具查看資料,也可以修改儲存的資料。 保護資料不一定是問題,因為資料本質上可能很平凡。 例如,讀取或修改 UI 元素的預存色彩,對使用者或組織來說並不具有重大的安全性風險。 避免允許使用者檢查或竄改敏感性資料

ASP.NET Core Protected Browser Storage

ASP.NET Core Protected Browser Storage 針對 localStoragesessionStorage 會運用 ASP.NET Core Data Protection

注意

受保護的瀏覽器儲存體會仰賴 ASP.NET Core Data Protection,且僅支援伺服器端 Blazor 應用程式。

警告

Microsoft.AspNetCore.ProtectedBrowserStorage 是不受支援的實驗性套件,不適合用於生產環境。

套件僅適用於 ASP.NET Core 3.1 應用程式。

組態

  1. 將套件參考新增至 Microsoft.AspNetCore.ProtectedBrowserStorage

    注意

    如需將套件新增至 .NET 應用程式的指引,請參閱在套件取用工作流程 (NuGet 文件)安裝及管理套件底下的文章。 在 NuGet.org 確認正確的套件版本。

  2. _Host.cshtml 檔案中,在結尾 </body> 標籤內新增下列指令碼:

    <script src="_content/Microsoft.AspNetCore.ProtectedBrowserStorage/protectedBrowserStorage.js"></script>
    
  3. Startup.ConfigureServices 中,呼叫 AddProtectedBrowserStorage 以將 localStoragesessionStorage 服務新增至服務集合:

    services.AddProtectedBrowserStorage();
    

儲存及載入元件中的資料

在任何需要將資料載入或儲存至瀏覽器儲存體的元件中,使用 @inject 指示詞插入下列任一項的執行個體:

  • ProtectedLocalStorage
  • ProtectedSessionStorage

選擇取決於您想要使用的瀏覽器儲存位置。 在以下範例中,已使用 sessionStorage

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@using 指示詞可以放在應用程式的 _Imports.razor 檔案中,而不是放在元件中。 使用 _Imports.razor 檔案會使命名空間可供應用程式的較大區段或整個應用程式使用。

若要根據 Blazor 專案範本在應用程式的 Counter 元件中保存 currentCount 值,請修改 IncrementCount 方法以使用 ProtectedSessionStore.SetAsync

private async Task IncrementCount()
{
    currentCount++;
    await ProtectedSessionStore.SetAsync("count", currentCount);
}

在較大型、更現實的應用程式中,個別欄位的儲存是不太可能發生的案例。 應用程式更可能儲存包含複雜狀態的整個模型物件。 ProtectedSessionStore 會自動序列化及還原序列化 JSON 資料,以儲存複雜的狀態物件。

在上述程式碼範例中,currentCount 資料會在使用者的瀏覽器中儲存為 sessionStorage['count']。 資料不會以純文字儲存,而是使用 ASP.NET Core Data Protection 來保護資料。 若在瀏覽器的開發人員主控台中評估 sessionStorage['count'],則會檢查加密的資料。

若要在稍後使用者返回 Counter 元件時復原 currentCount 資料,包括使用者位於新的線路上,請使用 ProtectedSessionStore.GetAsync

protected override async Task OnInitializedAsync()
{
    var result = await ProtectedSessionStore.GetAsync<int>("count");
    currentCount = result.Success ? result.Value : 0;
}
protected override async Task OnInitializedAsync()
{
    currentCount = await ProtectedSessionStore.GetAsync<int>("count");
}

如果元件的參數包含瀏覽狀態,請在 OnParametersSetAsync 中呼叫 ProtectedSessionStore.GetAsync 並指派非 null 結果,而不是 OnInitializedAsync。 僅在第一次具現化元件時,才會呼叫 OnInitializedAsync 一次。 如果使用者在相同頁面上繼續瀏覽至不同的 URL,則稍後不會再次呼叫 OnInitializedAsync。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

警告

本節中的範例只有在伺服器未啟用預先轉譯時才能運作。 啟用預先轉譯時,會產生錯誤,說明無法發出 JavaScript Interop 呼叫,因為元件已預先轉譯。

停用預先轉譯或新增其他程式碼以使用預先轉譯。 若要深入了解撰寫可與預先轉譯搭配運作的程式碼,請參閱處理預先轉譯一節。

處理載入狀態

由於瀏覽器儲存體是透過網路連線以非同步方式存取,因此在載入資料並提供給元件之前,一律會有一段時間。 為了獲得最佳結果,在載入進行時轉譯訊息,而不是顯示空白或預設資料。

其中一種方法是追蹤資料是否為 null,這表示資料仍在載入中。 在預設 Counter 元件中,計數會保留在 int 中。 將問號 (?) 新增至 (int) 類型,使 currentCount 可為 Null

private int? currentCount;

僅在檢查 HasValue 來載入資料時,才會顯示這些元素,而不是無條件顯示計數和 Increment 元素:

@if (currentCount.HasValue)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

處理預先轉譯

在預先轉譯期間:

  • 使用者瀏覽器的互動式連線並不存在。
  • 瀏覽器還沒有可以執行 JavaScript 程式碼的頁面。

localStoragesessionStorage 無法在預先轉譯期間使用。 如果元件嘗試與儲存體互動,就會產生錯誤,說明無法發出 JavaScript Interop 呼叫,因為元件已預先轉譯。

解決錯誤的其中一種方法是停用預先轉譯。 如果應用程式大量使用瀏覽器型儲存體,這通常是最佳選擇。 預先轉譯會增加複雜度,且不會使應用程式受益,因為應用程式在 localStoragesessionStorage 可用之前,無法預先轉譯任何有用的內容。

若要停用預先轉譯,請指出轉譯模式,並在應用程式元件階層的最高層元件 (不是根元件) 中將 prerender 參數設定為 false

注意

不支援讓根元件成為互動式,例如 App 元件。 因此,App 元件無法直接停用預先轉譯。

針對以 Blazor Web 應用程式專案範本為基礎的應用程式,預先轉譯通常會停用,其中 Routes 元件是在 App 元件 (Components/App.razor) 中使用:

<Routes @rendermode="new InteractiveServerRenderMode(prerender: false)" />

此外,停用元件的 HeadOutlet 預先轉譯:

<HeadOutlet @rendermode="new InteractiveServerRenderMode(prerender: false)" />

如需詳細資訊,請參閱 ASP.NET Core Blazor 轉譯模式

若要停用預先轉譯,請開啟 _Host.cshtml 檔案,並將元件標籤協助程式render-mode 屬性變更為 Server

<component type="typeof(App)" render-mode="Server" />

停用預先轉譯時,會停用 <head> 內容預先轉譯

預先轉譯對於不使用 localStoragesessionStorage 的其他頁面可能很有用。 若要保留預先轉譯,請延遲載入作業,直到瀏覽器連線到線路為止。 以下是儲存計數器值的範例:

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount;
    private bool isConnected;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        var result = await ProtectedLocalStore.GetAsync<int>("count");
        currentCount = result.Success ? result.Value : 0;
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedLocalStorage ProtectedLocalStore

@if (isConnected)
{
    <p>Current count: <strong>@currentCount</strong></p>
    <button @onclick="IncrementCount">Increment</button>
}
else
{
    <p>Loading...</p>
}

@code {
    private int currentCount = 0;
    private bool isConnected = false;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            isConnected = true;
            await LoadStateAsync();
            StateHasChanged();
        }
    }

    private async Task LoadStateAsync()
    {
        currentCount = await ProtectedLocalStore.GetAsync<int>("count");
    }

    private async Task IncrementCount()
    {
        currentCount++;
        await ProtectedLocalStore.SetAsync("count", currentCount);
    }
}

將狀態保留分解成一般位置

如果許多元件仰賴瀏覽器型儲存體,則實作狀態提供者程式碼在大多數情況下會建立程式碼重複。 避免程式碼重複的其中一個選項是建立封裝狀態提供者邏輯的狀態提供者父元件。 子元件可以處理保存的資料,而不考慮狀態持續性機制。

在下列 CounterStateProvider 元件的範例中,計數器資料會保存至 sessionStorage

@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

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

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        var result = await ProtectedSessionStore.GetAsync<int>("count");
        CurrentCount = result.Success ? result.Value : 0;
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}
@using Microsoft.AspNetCore.ProtectedBrowserStorage
@inject ProtectedSessionStorage ProtectedSessionStore

@if (isLoaded)
{
    <CascadingValue Value="this">
        @ChildContent
    </CascadingValue>
}
else
{
    <p>Loading...</p>
}

@code {
    private bool isLoaded;

    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public int CurrentCount { get; set; }

    protected override async Task OnInitializedAsync()
    {
        CurrentCount = await ProtectedSessionStore.GetAsync<int>("count");
        isLoaded = true;
    }

    public async Task SaveChangesAsync()
    {
        await ProtectedSessionStore.SetAsync("count", CurrentCount);
    }
}

注意

如需關於 RenderFragment 的詳細資訊,請參閱 ASP.NET Core Razor 元件

CounterStateProvider 元件會在狀態載入完成之前不轉譯其子內容來處理載入階段。

若要讓應用程式中的所有元件都能存取狀態,請使用全域互動式伺服器端轉譯 (互動式 SSR) 在 Routes 元件中的 Router (<Router>...</Router>) 周圍包上 CounterStateProvider 元件。

App 元件 (Components/App.razor) 中:

<Routes @rendermode="InteractiveServer" />

Routes 元件 (Components/Routes.razor) 中:

若要使用 CounterStateProvider 元件,請將元件執行個體包裝在需要存取計數器狀態的任何其他元件周圍。 若要讓應用程式中的所有元件都能存取狀態,請在 App 元件 (App.razor) 中,以 Router 包裝 CounterStateProvider 元件:

<CounterStateProvider>
    <Router ...>
        ...
    </Router>
</CounterStateProvider>

注意

隨著 ASP.NET Core 5.0.1 的發行以及在任何其他 5.x 版中,Router 元件會包含設定為 @truePreferExactMatches 參數。 如需詳細資訊,請參閱從 ASP.NET Core 3.1 移轉至 5.0

包裝的元件會接收並可修改保存的計數器狀態。 下列 Counter 元件會實作模式:

@page "/counter"

<p>Current count: <strong>@CounterStateProvider?.CurrentCount</strong></p>
<button @onclick="IncrementCount">Increment</button>

@code {
    [CascadingParameter]
    private CounterStateProvider? CounterStateProvider { get; set; }

    private async Task IncrementCount()
    {
        if (CounterStateProvider is not null)
        {
            CounterStateProvider.CurrentCount++;
            await CounterStateProvider.SaveChangesAsync();
        }
    }
}

不需要上述元件即可與 ProtectedBrowserStorage 互動,且上述元件也不會處理「載入」階段。

若要如先前所述處理預先轉譯,可以修改 CounterStateProvider,讓取用計數器資料的所有元件都自動使用預先轉譯。 如需詳細資訊,請參閱處理預先轉譯一節。

一般而言,建議使用狀態提供者父元件模式:

  • 若要在多個元件之間取用狀態。
  • 如果只有一個最上層狀態物件可保存。

若要保存許多不同的狀態物件,並在不同位置取用不同的物件子集,最好避免全域保存狀態。

在 Blazor WebAssembly 應用程式中建立的使用者狀態會保留在瀏覽器的記憶體中。

瀏覽器記憶體中保留的使用者狀態範例包括:

  • 元件執行個體的階層及其轉譯 UI 中最新的轉譯輸出。
  • 元件執行個體中的欄位和屬性值。
  • 保存在相依性插入 (DI) 服務執行個體中的資料。
  • 透過 JavaScript Interop 呼叫設定的值。

當使用者關閉並重新開啟其瀏覽器或重新載入頁面時,保存在瀏覽器記憶體中的使用者狀態會遺失。

注意

受保護的瀏覽器儲存體 (Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage 命名空間) 會仰賴 ASP.NET Core Data Protection,且僅支援伺服器端 Blazor 應用程式。

跨瀏覽器工作階段保存狀態

一般而言,會在使用者主動建立資料的瀏覽器工作階段之間維護狀態,而不只是讀取已經存在的資料。

若要跨瀏覽器工作階段保留狀態,應用程式必須將資料保存到瀏覽器記憶體以外的其他儲存位置。 狀態持續性並非自動。 開發應用程式以實作具狀態資料持續性時,您必須採取一些步驟。

通常只有使用者花費大量精力建立的高價值狀態才需要資料持續性。 在下列範例中,保存狀態可節省時間,或協助商務工作:

  • 多步驟 Web 表單:如果使用者的狀態遺失,則重新輸入多步驟 Web 表單數個已完成步驟的資料,對於使用者會相當耗時。 如果使用者離開表單並在稍後返回,則在此案例中就會失去狀態。
  • 購物車:可以維護代表潛在營收應用程式的任何商業重要元件。 失去狀態的使用者連同他們的購物車,在稍後返回網站時,可能會購買較少的產品或服務。

應用程式只能保存應用程式狀態。 無法保存 UI,例如元件執行個體及其轉譯樹狀結構。 元件和轉譯樹狀結構通常無法序列化。 若要保存 UI 狀態 (例如樹狀檢視控制項的展開節點),應用程式必須使用自訂程式碼,將 UI 狀態的行為模型化為可序列化的應用程式狀態。

保存狀態的位置

保存狀態的常見位置存在:

伺服器端儲存體

針對跨多個使用者和裝置的永久資料持續性,應用程式可以使用透過 Web API 存取的獨立伺服器端儲存體。 這些選項包括:

  • Blob 儲存體
  • 金鑰值儲存體
  • 關聯式資料庫
  • 表格儲存體

儲存資料之後,會保留使用者的狀態,並可在任何新的瀏覽器工作階段中使用。

由於 Blazor WebAssembly 應用程式完全在使用者的瀏覽器中執行,因此需要額外的量值來存取安全的外部系統,例如儲存體服務和資料庫。 Blazor WebAssembly 應用程式的保護方式與單頁應用程式 (SPA) 相同。 一般而言,應用程式會透過 OAuth/OpenID Connect (OIDC) 驗證使用者,然後透過 Web API 呼叫伺服器端應用程式與儲存體服務和資料庫互動。 伺服器端應用程式會調解 Blazor WebAssembly 應用程式與儲存體服務或資料庫之間的資料傳輸。 Blazor WebAssembly 應用程式會維護與伺服器端應用程式的暫時連線,而伺服器端應用程式則持續連線至儲存體。

如需詳細資訊,請參閱以下資源:

如需 Azure 資料儲存體選項的詳細資訊,請參閱下列各項:

URL

若為代表瀏覽狀態的暫時性資料,請將資料模型化為 URL 的一部分。 在 URL 中建立模型的使用者狀態範例包括:

  • 已檢視實體的識別碼。
  • 分頁方格中的目前頁碼。

如果使用者手動重新載入頁面,則會保留瀏覽器網址列的內容。

如需使用 @page 指示詞定義 URL 模式的資訊,請參閱 ASP.NET Core Blazor 路由和導覽

瀏覽器儲存體

針對使用者正在主動建立的暫時性資料,常用的儲存位置是瀏覽器的 localStoragesessionStorage 集合:

  • localStorage 範圍設定為瀏覽器的視窗。 如果使用者重新載入頁面或關閉並重新開啟瀏覽器,會保存狀態。 如果使用者開啟多個瀏覽器索引標籤,狀態會跨索引標籤共用。 資料會持續存在 localStorage 中,直到明確清除為止。
  • sessionStorage 範圍設定為瀏覽器索引標籤。如果使用者重新載入索引標籤,則狀態會持續。 如果使用者關閉索引標籤或瀏覽器,狀態就會遺失。 如果使用者開啟多個瀏覽器索引標籤,則每個索引標籤都有自己的獨立資料版本。

注意

localStoragesessionStorage 可用於 Blazor WebAssembly 應用程式中,但只能藉由撰寫自訂程式碼或使用協力廠商套件。

一般而言,sessionStorage 使用上更安全。 sessionStorage 可避免使用者開啟多個索引標籤並遇到下列風險:

  • 跨索引標籤在狀態儲存體中的錯誤。
  • 當索引標籤覆寫其他索引標籤的狀態時,造成混淆的行為。

如果應用程式必須在關閉並重新開啟瀏覽器時保存狀態,則 localStorage 為較佳的選擇。

警告

使用者可以檢視或竄改儲存在 localStoragesessionStorage 中的資料。

記憶體內部狀態容器服務

巢狀元件通常會使用鏈結繫結來繫結資料,如 ASP.NET Core Blazor 資料繫結中所述。 巢狀和未巢狀的元件可以使用已註冊的記憶體內部狀態容器來共用資料的存取權。 自訂狀態容器類別可以使用可指派的 Action,在應用程式的不同部分通知元件狀態變更。 在以下範例中:

  • 一組元件會使用狀態容器來追蹤屬性。
  • 下列範例中的一個元件是巢狀於另一個元件中,但此方法不需要巢狀即可運作。

重要

本節中的範例示範如何建立記憶體內部狀態容器服務、註冊服務,以及在元件中使用服務。 此範例不會在沒有進一步開發的情況下保存資料。 為了持續儲存資料,狀態容器必須採用在清除瀏覽器記憶體時存留的基礎儲存機制。 這可以透過 localStorage/sessionStorage 或一些其他技術來完成。

StateContainer.cs

public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

用戶端應用程式 (Program 檔案):

builder.Services.AddSingleton<StateContainer>();

伺服器端應用程式 (Program 檔案、.NET 6 或更新版本中的 ASP.NET Core):

builder.Services.AddScoped<StateContainer>();

伺服器端應用程式 (Startup.csStartup.ConfigureServices、ASP.NET Core 早於 6.0 版本):

services.AddScoped<StateContainer>();

Shared/Nested.razor

@implements IDisposable
@inject StateContainer StateContainer

<h2>Nested component</h2>

<p>Nested component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the Nested component
    </button>
</p>

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = 
            $"New value set in the Nested component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

StateContainerExample.razor

@page "/state-container-example"
@implements IDisposable
@inject StateContainer StateContainer

<h1>State Container Example component</h1>

<p>State Container component Property: <b>@StateContainer.Property</b></p>

<p>
    <button @onclick="ChangePropertyValue">
        Change the Property from the State Container Example component
    </button>
</p>

<Nested />

@code {
    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
    }

    private void ChangePropertyValue()
    {
        StateContainer.Property = "New value set in the State " +
            $"Container Example component: {DateTime.Now}";
    }

    public void Dispose()
    {
        StateContainer.OnChange -= StateHasChanged;
    }
}

上述元件會實作 IDisposable,且 OnChange 委派會取消訂閱 Dispose 方法,在處置元件時架構會呼叫這些方法。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件生命週期

其他方法

實作自訂狀態儲存體時,有用的方法是採用串聯的值及參數

  • 若要在多個元件之間取用狀態。
  • 如果只有一個最上層狀態物件可保存。

疑難排解

在自訂狀態管理服務中,在 Blazor 同步處理內容外部叫用的回呼必須包裝 ComponentBase.InvokeAsync 中回呼的邏輯,才能將其移至轉譯器的同步處理內容。

當狀態管理服務未呼叫 Blazor 同步處理內容的 StateHasChanged 時,會擲回下列錯誤:

System.InvalidOperationException:「目前的執行緒與 Dispatcher 沒有關聯。 在觸發轉譯或元件狀態時,使用 InvokeAsync() 將執行切換至 Dispatcher」。

如需如何解決此錯誤的詳細資訊和範例,請參閱 ASP.NET Core Razor 元件轉譯

其他資源