Каскадные значения и параметры ASP.NET Core Blazor
Примечание.
Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.
Предупреждение
Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.
Внимание
Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.
В текущем выпуске см . версию .NET 9 этой статьи.
В этой статье содержатся сведения о передаче данных из компонента-предка Razor в компоненты-потомки.
Каскадные значения и параметры — это удобный способ передачи данных по иерархии компонентов из компонента-предка в любое количество компонентов, расположенных ниже в иерархии. В отличие от параметров компонента, каскадные значения и параметры не требуют назначения атрибута для каждого компонента-потомка, где используются данные. Каскадные значения и параметры также позволяют компонентам согласовываться друг с другом в иерархии компонентов.
Примечание.
Примеры кода в этой статье используют типы ссылок, допускающие значение NULL (NRTs) и статический анализ состояния .NET компилятора NULL, которые поддерживаются в ASP.NET Core в .NET 6 или более поздней версии. При назначении ASP.NET Core 5.0 или более ранней версии удалите обозначение типа NULL (?
) из CascadingType?
, , @ActiveTab?
, TabSet?
RenderFragment?
ITab?
и string?
типы в примерах статьи.
Каскадные значения корневого уровня
Каскадные значения корневого уровня можно зарегистрировать для всей иерархии компонентов. Поддерживаются именованные каскадные значения и подписки для уведомлений об обновлении.
Следующий класс используется в примерах этого раздела.
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; }
}
// "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:
Dalek
значение свойстваUnits
для зарегистрировано в качестве фиксированного каскадного значения.- Вторая
Dalek
регистрация с другим значением свойства дляUnits
имеет имя "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; }
}
@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
Компонент-предок предоставляет каскадное значение с помощью компонента CascadingValue
платформы Blazor, который заключает поддерево иерархии компонентов и предоставляет одно значение для всех компонентов в его поддереве.
В следующем примере показан поток сведений о теме вниз иерархии компонентов, чтобы предоставить класс стилей CSS к кнопкам в дочерних компонентах.
ThemeInfo
Следующий класс C# указывает сведения о теме.
Примечание.
В примерах в этом разделе используется пространство имен приложения BlazorSample
. Экспериментируя с кодом в собственном примере приложения, измените пространство имен приложения на пространство имен своего примера приложения.
ThemeInfo.cs
:
namespace BlazorSample;
public class ThemeInfo
{
public string? ButtonClass { get; set; }
}
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; }
}
}
Следующий компонент макетаBody задает сведения о теме оформления () как каскадное значение для всех компонентов, составляющих текст макета свойства ThemeInfo
. ButtonClass
присваивается значение btn-success
, которое является стилем кнопки начальной загрузки. Любой компонент-потомок в иерархии компонентов может использовать свойство ButtonClass
через каскадное значение ThemeInfo
.
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>
<span class="dismiss">🗙</span>
</div>
@code {
private ThemeInfo theme = new() { ButtonClass = "btn-success" };
}
@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 Apps предоставляют альтернативные подходы к каскадным значениям, которые применяются более широко к приложению, чем их мебель с помощью одного файла макета:
Заключите разметку
Routes
компонента в компонент, чтобы указать данные вCascadingValue
виде каскадного значения для всех компонентов приложения.В следующем примере каскады
ThemeInfo
данных изRoutes
компонента.Routes.razor
:<CascadingValue Value="theme"> <Router ...> ... </Router> </CascadingValue> @code { private ThemeInfo theme = new() { ButtonClass = "btn-success" }; }
Примечание.
Оболочка экземпляра
Routes
App
компонента в компоненте (Components/App.razor
) с компонентомCascadingValue
не поддерживается.Укажите каскадное значение корневого уровня в качестве службы, вызвав AddCascadingValue метод расширения в построителе коллекций служб.
В следующем примере каскадные
ThemeInfo
данные изProgram
файла.Program.cs
builder.Services.AddCascadingValue(sp => new ThemeInfo() { ButtonClass = "btn-primary" });
Дополнительные сведения см. в следующих разделах этой статьи:
Атрибут [CascadingParameter]
Чтобы использовать каскадные значения, компоненты-потомки объявляют каскадные параметры с помощью атрибута [CascadingParameter]
. Каскадные значения привязаны к каскадным параметрам по типу. Каскадное применение нескольких значений одного и того же типа рассматривается в разделе Каскадное применение нескольких значений далее в этой статье.
Следующий компонент привязывает каскадное значение ThemeInfo
к каскадному параметру, при необходимости используя то же имя ThemeInfo
. Параметр используется для задания класса CSS для кнопки Increment Counter (Themed)
.
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"
<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
rerender.
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 С помощью платформы параметры, передаваемые через статический SSR на границу интерактивной отрисовки, сериализуются автоматически, если они сериализуются в формате JSON или возникает ошибка.
- Состояние, хранящееся в
PersistentComponentState
сериализованном и восстановленном автоматически, если оно сериализуется в формате JSON или возникает ошибка.
Каскадные параметры не сериализуются в формате JSON, так как типичные шаблоны использования каскадных параметров несколько похожи на службы DI. Часто существуют варианты каскадных параметров для конкретной платформы, поэтому разработчики не смогут использовать платформу, если платформа перестала использовать серверные интерактивные версии или версии WebAssembly. Кроме того, многие каскадные значения параметров в целом не сериализуются, поэтому было бы нецелесообразно обновить существующие приложения, если вам пришлось остановить использование всех несериализируемых каскадных значений параметров.
Рекомендации.
Если необходимо сделать состояние доступным для всех интерактивных компонентов в качестве каскадного параметра, рекомендуется использовать каскадные значения корневого уровня. Шаблон фабрики доступен, и приложение может выдавать обновленные значения после запуска приложения. Каскадные значения корневого уровня доступны для всех компонентов, включая интерактивные компоненты, так как они обрабатываются как службы DI.
Для авторов библиотек компонентов можно создать метод расширения для потребителей библиотеки, как показано ниже.
builder.Services.AddLibraryCascadingParameters();
Поручите разработчикам вызывать метод расширения. Это звуковая альтернатива указаниям им добавить
<RootComponent>
компонент в ихMainLayout
компонент.
Каскадное применение нескольких значений
Чтобы выполнить каскадное применение нескольких значений одного типа в одном поддереве, укажите уникальную строку Name для каждого компонента CascadingValue
и его соответствующих атрибутов [CascadingParameter]
.
В следующем примере два компонента 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; }
}
Передача данных в иерархии компонентов
Каскадные параметры также позволяют компонентам передавать данные в рамках иерархии компонентов. Рассмотрим следующий пример набора вкладок пользовательского интерфейса, в котором компонент набора вкладок поддерживает ряд отдельных вкладок.
Примечание.
В примерах в этом разделе используется пространство имен приложения BlazorSample
. Экспериментируя с кодом в собственном примере приложения, измените пространство имен приложения на пространство имен своего примера приложения.
Создайте интерфейс ITab
, который реализуется вкладками в папке с именем UIInterfaces
.
UIInterfaces/ITab.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.UIInterfaces;
public interface ITab
{
RenderFragment ChildContent { get; }
}
Примечание.
Дополнительные сведения о RenderFragment см. в статье Компоненты Razor ASP.NET Core.
Следующий компонент TabSet
содержит набор вкладок. Компоненты набора вкладок Tab
, которые создаются далее в этом разделе, предоставляют элементы списка (<li>...</li>
) для списка (<ul>...</ul>
).
Дочерние компоненты 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
использует компонент TabSet
, содержащий три компонента Tab
.
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;
}
Дополнительные ресурсы
ASP.NET Core