ASP.NET Core Blazor 串聯值和參數

注意

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

重要

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

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

本文會說明如何將資料從父代 Razor 元件流向子代元件。

串聯值和參數 提供方便的方式,將資料從父代元件階層流向任何數目的子代元件。 不同於 元件參數,串聯值和參數不需要針對取用資料的每個子代元件指派屬性。 串聯值和參數也允許元件跨元件階層彼此協調。

注意

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

根層級串聯值

可以登錄整個元件階層的根層級串聯值。 支援更新通知的具名串聯值和訂用帳戶。

本章節的範例會使用下列類別。

Dalek.cs

// "Dalek" ©Terry Nation https://www.imdb.com/name/nm0622334/
// "Doctor Who" ©BBC https://www.bbc.co.uk/programmes/b006q2x0

namespace BlazorSample;

public class Dalek
{
    public int Units { get; set; }
}

會在應用程式的 Program 檔案中使用 AddCascadingValue 進行下列註冊:

  • 具有 Units 屬性值的 Dalek 會註冊為固定串聯值。
  • 具有不同 Units 屬性值的第二個 Dalek 註冊被命名為 「 AlphaGroup 」。
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

下列 Daleks 元件會顯示串聯的值。

Daleks.razor

@page "/daleks"

<PageTitle>Daleks</PageTitle>

<h1>Root-level Cascading Value Example</h1>

<ul>
    <li>Dalek Units: @Dalek?.Units</li>
    <li>Alpha Group Dalek Units: @AlphaGroupDalek?.Units</li>
</ul>

<p>
    Dalek© <a href="https://www.imdb.com/name/nm0622334/">Terry Nation</a><br>
    Doctor Who© <a href="https://www.bbc.co.uk/programmes/b006q2x0">BBC</a>
</p>

@code {
    [CascadingParameter]
    public Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

在下列範例中, Dalek 會使用 CascadingValueSource<T> 註冊為串聯值,其中 <T> 是型別。 isFixed 旗標會指出值是否為固定。 如果為 false,則所有收件人都會訂閱更新通知,而更新通知則由呼叫 NotifyChangedAsync 發出。 訂用帳戶會建立額外負荷並降低效能,因此如果值未變更,請將 isFixed 設定為 true

builder.Services.AddCascadingValue(sp =>
{
    var dalek = new Dalek { Units = 789 };
    var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);
    return source;
});

警告

將元件類型註冊為根層級串聯值,並不會為該類型註冊其他服務,或允許元件中的服務啟用。

將必要的服務與串聯值分開處理,並將其與串聯類型分開註冊。

避免使用 AddCascadingValue 將元件類型註冊為串聯值。 相反地,將 <Router>...</Router> 與元件一起包裝在 Routes 元件中 (Components/Routes.razor),並採用全域互動式伺服器端轉譯 (互動式 SSR)。 如需範例,請參閱 CascadingValue 元件一節。

CascadingValue 元件

父代元件會使用 Blazor 架構的 CascadingValue 元件提供串聯值,它會包裝元件階層的子樹,並將單一值提供給其子樹內的所有元件。

下列範例示範元件階層中的主題資訊流程,以提供 CSS 樣式類別給子代元件中的按鈕。

下列 ThemeInfo C# 類別會指定主題資訊。

注意

在本章節中的範例中,應用程式的命名空間為 BlazorSample。 在您自己的範例應用程式中實驗程式碼時,請將應用程式的命名空間變更為範例應用程式的命名空間。

ThemeInfo.cs

namespace BlazorSample;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses;

public class ThemeInfo
{
    public string? ButtonClass { get; set; }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}
namespace BlazorSample.UIThemeClasses
{
    public class ThemeInfo
    {
        public string ButtonClass { get; set; }
    }
}

下列 配置元件 會將主題資訊 (ThemeInfo) 指定為構成 Body 屬性配置主體之所有元件的串聯值。 ButtonClass 被指派的值為 btn-success,這是個 Bootstrap 按鈕樣式。 元件階層中的任何子代元件都可以透過 ThemeInfo 串聯值使用 ButtonClass 屬性。

MainLayout.razor

@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <CascadingValue Value="@theme">
            <article class="content px-4">
                @Body
            </article>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </main>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <div class="main">
        <CascadingValue Value="@theme">
            <div class="content px-4">
                @Body
            </div>
        </CascadingValue>
    </div>
</div>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@inherits LayoutComponentBase
@using BlazorSample.UIThemeClasses

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <CascadingValue Value="theme">
        <div class="content px-4">
            @Body
        </div>
    </CascadingValue>
</div>

@code {
    private ThemeInfo theme = new ThemeInfo { ButtonClass = "btn-success" };
}

Blazor Web 應用程式提供替代方法,讓串聯值比起透過單一配置檔案提供,更廣泛地套用到應用程式:

  • Routes 元件的標記包裝在 CascadingValue 元件中,以將所有應用程式元件的資料指定為串聯值。

    下列範例會串聯來自 Routes 元件的 ThemeInfo 資料。

    Routes.razor

    <CascadingValue Value="theme">
        <Router ...>
            ...
        </Router>
    </CascadingValue>
    
    @code {
        private ThemeInfo theme = new() { ButtonClass = "btn-success" };
    }
    

    注意

    支援將 Routes 元件執行個體與 CascadingValue 元件一起包裝在 App 元件 (Components/App.razor) 中。

  • 藉由在服務集合建立器上呼叫 AddCascadingValue 擴充方法,將 根層級串聯值 指定為服務。

    下列範例會串聯來自 Program 檔案的 ThemeInfo 資料。

    Program.cs

    builder.Services.AddCascadingValue(sp => 
        new ThemeInfo() { ButtonClass = "btn-primary" });
    

如需詳細資訊,請參閱本文的下列章節:

[CascadingParameter] 屬性

若要確認串聯值,子代元件會使用 [CascadingParameter]屬性 宣告串聯參數。 串聯值會依類型繫結至串聯參數。 本文稍後的 串聯多個值 章節會涵蓋相同類型的多個值串聯。

下列元件會將 ThemeInfo 串聯值繫結至串聯參數,選擇性地使用 ThemeInfo 的相同名稱。 參數是用來設定 Increment Counter (Themed) 按鈕的 CSS 類別。

ThemedCounter.razor

@page "/themed-counter"

<PageTitle>Themed Counter</PageTitle>

<h1>Themed Counter Example</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button 
        class="btn @(ThemeInfo is not null ? ThemeInfo.ButtonClass : string.Empty)" 
        @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo? ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/themed-counter"
@using BlazorSample.UIThemeClasses

<h1>Themed Counter</h1>

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

<p>
    <button @onclick="IncrementCount">
        Increment Counter (Unthemed)
    </button>
</p>

<p>
    <button class="btn @ThemeInfo.ButtonClass" @onclick="IncrementCount">
        Increment Counter (Themed)
    </button>
</p>

@code {
    private int currentCount = 0;

    [CascadingParameter]
    protected ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}

與一般元件參數類似,當串聯值變更時,接受串聯參數的元件會重新呈現。 例如,設定不同的主題執行個體會導致 CascadingValue 元件 區段中的 ThemedCounter 元件重新調整。

MainLayout.razor

<main>
    <div class="top-row px-4">
        <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
    </div>

    <CascadingValue Value="theme">
        <article class="content px-4">
            @Body
        </article>
    </CascadingValue>
    <button @onclick="ChangeToDarkTheme">Dark mode</button>
</main>

@code {
    private ThemeInfo theme = new() { ButtonClass = "btn-success" };

    private void ChangeToDarkTheme()
    {
        theme = new() { ButtonClass = "btn-secondary" };
    }
}

CascadingValue<TValue>.IsFixed 可以用來指出串聯參數在初始化之後不會變更。

串聯值/參數和轉譯模式界限

串聯參數不會跨轉譯模式界限傳遞資料:

  • 互動式工作階段會在與使用靜態伺服器端轉譯 (靜態 SSR) 的頁面不同的內容中執行。 產生頁面的伺服器甚至不需要裝載一些稍後的互動式伺服器工作階段的相同電腦,包括伺服器是與用戶端不同電腦的 WebAssembly 元件。 靜態伺服器端轉譯 (靜態 SSR) 的優點是取得純粹無狀態 HTML 轉譯的完整效能。

  • 在靜態和互動式轉譯之間跨越界限的狀態必須可序列化。 元件是參考大量其他物件鏈結的任意物件,包括轉譯器、DI 容器和每個 DI 服務執行個體。 您必須明確地讓狀態從靜態 SSR 序列化,使其可在後續的互動式轉譯元件中使用。 採用兩種方法:

    • 透過 Blazor 架構,如果參數是 JSON 可序列化或擲回錯誤,則跨靜態 SSR 傳遞至互動式轉譯界限的參數會自動序列化。
    • 儲存在 PersistentComponentState 中的狀態會在 JSON 可序列化或擲回錯誤時自動復原。

串聯參數並非 JSON 可序列化,因為串聯參數的典型使用模式有點類似 DI 服務。 通常有平台特定的串聯參數變體,因此,如果架構阻止開發人員擁有伺服器互動式特定版本或 WebAssembly 特定版本,開發人員就無計可施。 此外,一般而言許多串聯參數值無法序列化,因此,如果您必須停止使用所有不可序列化的串聯參數值,更新現有的應用程式是不切實際的。

建議:

  • 如果您需要將所有互動式元件的狀態設為串聯參數,建議您使用根層級串聯值。 處理站模式可供使用,應用程式可以在應用程式啟動之後發出更新的值。 根層級串聯值可供所有元件使用,包括互動式元件,因為它們會當作 DI 服務進行處理。

  • 針對元件程式庫作者,您可以為程式庫取用者建立擴充方法,如下所示:

    builder.Services.AddLibraryCascadingParameters();
    

    指示開發人員呼叫擴充方法。 這是指示他們在其 MainLayout 元件中新增 <RootComponent> 元件的合理替代方案。

串聯多個值

若要串聯相同子樹內相同類型的多個值,請為每個 CascadingValue 元件及其對應的 [CascadingParameter] 屬性 提供唯一的 Name 字串。

在下列範例中,兩個 CascadingValue 元件會串聯 CascadingType 不同的執行個體:

<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
    <CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
        ...
    </CascadingValue>
</CascadingValue>

@code {
    private CascadingType? parentCascadeParameter1;

    [Parameter]
    public CascadingType? ParentCascadeParameter2 { get; set; }
}

在子代元件中,串聯參數會透過 Name 從父代元件接收其串聯值:

@code {
    [CascadingParameter(Name = "CascadeParam1")]
    protected CascadingType? ChildCascadeParameter1 { get; set; }

    [CascadingParameter(Name = "CascadeParam2")]
    protected CascadingType? ChildCascadeParameter2 { get; set; }
}

跨元件階層傳遞資料

串聯參數也可讓元件跨元件階層傳遞資料。 請考慮下列 UI 索引標籤集範例,其中索引標籤集元件會維護一系列的個別索引標籤。

注意

在本章節中的範例中,應用程式的命名空間為 BlazorSample。 在您自己的範例應用程式中實驗程式碼時,請將應用程式的命名空間變更為範例應用程式的命名空間。

建立 ITab 介面,以在名為 UIInterfaces 的資料夾中實作索引標籤。

UIInterfaces/ITab.cs

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

注意

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

下列 TabSet 元件會保持一組索引標籤。 在本章節稍後建立的索引標籤集的 Tab 元件會提供清單 (<ul>...</ul>) 的清單專案 (<li>...</li>)。

子代 Tab 元件不會明確當做參數傳遞至 TabSet。 相反地,子代 Tab 元件是 TabSet 子內容的一部分。 不過,TabSet 仍然需要每個 Tab 元件的參考,以便轉譯標頭和使用中索引標籤。若要在不需額外程式碼的情況下啟用此協調,TabSet 元件 可以提供本身做為串聯值,然後由子代 Tab 元件挑選。

TabSet.razor

@using BlazorSample.UIInterfaces

<!-- Display the tab headers -->

<CascadingValue Value="this">
    <ul class="nav nav-tabs">
        @ChildContent
    </ul>
</CascadingValue>

<!-- Display body for only the active tab -->

<div class="nav-tabs-body p-4">
    @ActiveTab?.ChildContent
</div>

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

    public ITab? ActiveTab { get; private set; }

    public void AddTab(ITab tab)
    {
        if (ActiveTab is null)
        {
            SetActiveTab(tab);
        }
    }

    public void SetActiveTab(ITab tab)
    {
        if (ActiveTab != tab)
        {
            ActiveTab = tab;
            StateHasChanged();
        }
    }
}

子代 Tab 元件會擷取包含的 TabSet 作爲串聯參數。 Tab 元件會自行新增至 TabSet 並協調設定使用中的索引標籤。

Tab.razor

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    public TabSet? ContainerTabSet { get; set; }

    [Parameter]
    public string? Title { get; set; }

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

    private string? TitleCssClass => 
        ContainerTabSet?.ActiveTab == this ? "active" : null;

    protected override void OnInitialized()
    {
        ContainerTabSet?.AddTab(this);
    }

    private void ActivateTab()
    {
        ContainerTabSet?.SetActiveTab(this);
    }
}

下列 ExampleTabSet 元件會使用其中包含三個 Tab 元件的 TabSet 元件。

ExampleTabSet.razor

@page "/example-tab-set"

<TabSet>
    <Tab Title="First tab">
        <h4>Greetings from the first tab!</h4>

        <label>
            <input type="checkbox" @bind="showThirdTab" />
            Toggle third tab
        </label>
    </Tab>

    <Tab Title="Second tab">
        <h4>Hello from the second tab!</h4>
    </Tab>

    @if (showThirdTab)
    {
        <Tab Title="Third tab">
            <h4>Welcome to the disappearing third tab!</h4>
            <p>Toggle this tab from the first tab.</p>
        </Tab>
    }
</TabSet>

@code {
    private bool showThirdTab;
}

其他資源