Поделиться через


Каскадные значения и параметры ASP.NET Core Blazor

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 9 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 9 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 9 этой статьи.

В этой статье содержатся сведения о передаче данных из компонента-предка Razor в компоненты-потомки.

Каскадные значения и параметры — это удобный способ передачи данных по иерархии компонентов из компонента-предка в любое количество компонентов, расположенных ниже в иерархии. В отличие от параметров компонента, каскадные значения и параметры не требуют назначения атрибута для каждого компонента-потомка, где используются данные. Каскадные значения и параметры также позволяют компонентам согласовываться друг с другом в иерархии компонентов.

Примечание.

Примеры кода в этой статье используют типы ссылок, допускающие значение NULL (NRTs) и статический анализ состояния .NET компилятора NULL, которые поддерживаются в ASP.NET Core в .NET 6 или более поздней версии. При использовании .NET 5 или более ранней версии удалите обозначение типа NULL (?) из типов CascadingType?, @ActiveTab?, RenderFragment?, ITab?, TabSet? и 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все получатели подписаны на уведомления об обновлении. Подписки создают издержки и снижают производительность, поэтому установите 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> компонент () компонентом RoutesComponents/Routes.razorи внедряйте глобальную интерактивную отрисовку на стороне сервера (интерактивная служба SSR). Пример см. в CascadingValue разделе компонента .

Каскадные значения на уровне корня с уведомлениями

Вызов NotifyChangedAsync для отправки уведомлений об обновлении может использоваться, чтобы уведомить нескольких подписчиков компонентов Razor о том, что каскадное значение изменилось. Уведомления недоступны для подписчиков, которые принимают статическую отрисовку на стороне сервера (статический SSR), поэтому подписчики должны использовать интерактивный режим отрисовки.

В следующем примере :

  • NotifyingDalek INotifyPropertyChanged реализуется для уведомления клиентов о том, что значение свойства изменилось. При установке свойства Units, вызывается PropertyChangedEventHandler (PropertyChanged).
  • Метод SetUnitsToOneThousandAsync можно активировать подписчиками, чтобы установить Units значение 1000 с имитированной задержкой обработки.

Помните, что в продуктивном коде любое изменение состояния (любое изменение значения свойства класса) вызывает повторную отрисовку всех подписанных компонентов, независимо от того, какую часть состояния они используют. Рекомендуется создавать детализированные классы, каскадно создавая их отдельно с определенными подписками, чтобы гарантировать, что на изменения влияют только компоненты, подписанные на определенную часть состояния приложения.

Примечание.

Blazor Web App Для решения, состоящего из проектов сервера и клиента (.Client), следующий NotifyingDalek.cs файл помещается в .Client проект.

NotifyingDalek.cs:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class NotifyingDalek : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private int units;

    public int Units
    {
        get => units;
        set
        {
            if (units != value)
            {
                units = value;
                OnPropertyChanged();
            }
        }
    }

    protected virtual void OnPropertyChanged(
        [CallerMemberName] string? propertyName = default)
            => PropertyChanged?.Invoke(this, new(propertyName));

    public async Task SetUnitsToOneThousandAsync()
    {
        // Simulate a three second delay in processing
        await Task.Delay(3000);

        Units = 1000;
    }
}

Из следующего CascadingStateServiceCollectionExtensions создается CascadingValueSource<TValue> из типа, реализующего INotifyPropertyChanged.

Примечание.

Blazor Web App Для решения, состоящего из проектов сервера и клиента (.Client), следующий CascadingStateServiceCollectionExtensions.cs файл помещается в .Client проект.

CascadingStateServiceCollectionExtensions.cs:

using System.ComponentModel;
using Microsoft.AspNetCore.Components;

namespace Microsoft.Extensions.DependencyInjection;

public static class CascadingStateServiceCollectionExtensions
{
    public static IServiceCollection AddNotifyingCascadingValue<T>(
        this IServiceCollection services, T state, bool isFixed = false)
        where T : INotifyPropertyChanged
    {
        return services.AddCascadingValue<T>(sp =>
        {
            return new CascadingStateValueSource<T>(state, isFixed);
        });
    }

    private sealed class CascadingStateValueSource<T>
        : CascadingValueSource<T>, IDisposable where T : INotifyPropertyChanged
    {
        private readonly T state;
        private readonly CascadingValueSource<T> source;

        public CascadingStateValueSource(T state, bool isFixed = false)
            : base(state, isFixed = false)
        {
            this.state = state;
            source = new CascadingValueSource<T>(state, isFixed);
            this.state.PropertyChanged += HandlePropertyChanged;
        }

        private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
        {
            _ = NotifyChangedAsync();
        }

        public void Dispose()
        {
            state.PropertyChanged -= HandlePropertyChanged;
        }
    }
}

Тип PropertyChangedEventHandler (HandlePropertyChanged) вызывает метод CascadingValueSource<TValue>NotifyChangedAsync, чтобы уведомить подписчиков о том, что каскадное значение изменилось. Task отбрасывается при вызове NotifyChangedAsync, так как вызов представляет только длительность отправки в синхронный контекст. Исключения обрабатываются внутри путем их отправки в рендерер в контексте того компонента, который их вызвал при получении обновления. Это аналогичный способ обработки исключений с CascadingValue<TValue>, который не уведомляется об исключениях, происходящих внутри получателей уведомлений. Обработчик событий отключен в методе Dispose , чтобы предотвратить утечку памяти.

Program В файле NotifyingDalek передается, чтобы создать CascadingValueSource<TValue> с начальным значением Unit, равным 888 единиц.

builder.Services.AddNotifyingCascadingValue(new NotifyingDalek() { Units = 888 });

Примечание.

Для Blazor Web App решения, состоящего из проектов сервера и клиента .Client, предыдущий код помещается в файл каждого проекта Program.

Следующий компонент используется для демонстрации того, как изменение значения NotifyingDalek.Units уведомляет подписчиков.

Daleks.razor:

<h2>Daleks component</h2>

<div>
    <b>Dalek Units:</b> @Dalek?.Units
</div>

<div>
    <label>
        <span style="font-weight:bold">New Unit Count:</span>
        <input @bind="dalekCount" />
    </label>
    <button @onclick="Update">Update</button>
</div>

<div>
    <button @onclick="SetOneThousandUnits">Set Units to 1,000</button>
</div>

<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 {
    private int dalekCount;

    [CascadingParameter]
    public NotifyingDalek? Dalek { get; set; }

    private void Update()
    {
        if (Dalek is not null)
        {
            Dalek.Units = dalekCount;
            dalekCount = 0;
        }
    }

    private async Task SetOneThousandUnits()
    {
        if (Dalek is not null)
        {
            await Dalek.SetUnitsToOneThousandAsync();
        }
    }
}

Чтобы продемонстрировать несколько уведомлений подписчика, следующий DaleksMain компонент отображает три Daleks компонента. При обновлении количества единиц одногоUnitsDalek компонента обновляются другие два Dalek подписчика компонентов.

DaleksMain.razor:

@page "/daleks-main"

<PageTitle>Daleks Main</PageTitle>

<h1>Daleks Main</h1>

<Daleks />

<Daleks />

<Daleks />

Добавьте ссылку навигации к компоненту DaleksMain в NavMenu.razor:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="daleks-main">
        <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Daleks
    </NavLink>
</div>

CascadingValueSource<TValue>Так как тип в этом примере (NotifyingDalek) является типом класса, вы можете соответствовать практически любому требованию спецификации функции управления состоянием. Однако подписки создают издержки и снижают производительность, поэтому тестируйте производительность этого подхода в приложении и сравнивайте их с другими подходами к управлению состояниями , прежде чем применять его в рабочем приложении с ограниченными ресурсами обработки и памяти.

Любое изменение состояния (любое изменение значения свойства класса) заставляет все подписанные компоненты перерисовываться, независимо от того, какая часть состояния ими используется. Избегайте создания одного большого класса, представляющего все глобальное состояние приложения. Вместо этого создайте детализированные классы и каскадируйте их отдельно с определенными подписками на каскадные параметры, обеспечивая влияние изменений только на компоненты, подписанные на определенную часть состояния приложения.

Компонент CascadingValue

Компонент-предок предоставляет каскадное значение с помощью компонента Blazor платформы CascadingValue, который заключает поддерево иерархии компонентов и предоставляет одно значение для всех компонентов в его поддереве.

В следующем примере показан поток сведений о теме вниз иерархии компонентов, чтобы предоставить класс стилей 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; }
    }
}

Следующий ) как каскадное значение для всех компонентов, составляющих текст макета свойства 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" };
    }
    

    Примечание.

    Оболочка экземпляра RoutesApp компонента в компоненте (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++;
    }
}

Как и в случае с обычным параметром компонента, компоненты, принимающие каскадные параметры, перерисовываются при изменении каскадного значения. Например, при настройке другогоThemedCounterCascadingValue вызывает 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;
}

Дополнительные ресурсы