Valeurs et paramètres en cascade ASP.NET Core Blazor
Remarque
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 8 de cet article.
Avertissement
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la Stratégie de prise en charge de .NET et .NET Core. Pour la version actuelle, consultez la version .NET 8 de cet article.
Important
Ces informations portent sur la préversion du produit, qui est susceptible d’être en grande partie modifié avant sa commercialisation. Microsoft n’offre aucune garantie, expresse ou implicite, concernant les informations fournies ici.
Pour la version actuelle, consultez la version .NET 8 de cet article.
Cet article explique comment faire passer le flux de données d’un composant Razor ancêtre aux composants descendants.
Les valeurs et paramètres en cascade offrent un moyen pratique de faire passer le flux de données vers le bas d’une hiérarchie de composants, d’un composant ancêtre vers un nombre quelconque de composants descendants. Contrairement aux paramètres de composant, les valeurs et paramètres en cascade ne nécessitent pas d’affectation d’attribut pour chaque composant descendant où les données sont consommées. Les valeurs et paramètres en cascade permettent également aux composants de se coordonner entre eux au sein d’une hiérarchie de composants.
Remarque
Les exemples de code dans cet article adoptent les types référence null (NRT, nullable reference types) et l'analyse statique de l'état nul du compilateur .NET qui sont pris en charge dans ASP.NET Core 6 ou une version ultérieure. Lorsque vous ciblez ASP.NET Core 5.0 ou version antérieure, supprimez la désignation de type Null (?
) des types CascadingType?
, @ActiveTab?
, RenderFragment?
, ITab?
, TabSet?
et string?
dans les exemples de l’article.
Valeurs en cascade du niveau racine
Les valeurs en cascade au niveau racine peuvent être inscrites pour la hiérarchie complète des composants. Les valeurs et abonnements nommés en cascade pour les notifications de mise à jour sont pris en charge.
La classe suivante est utilisée dans les exemples de cette section.
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; }
}
Les inscriptions suivantes sont effectuées dans le fichier Program
de l’application avec AddCascadingValue :
- Avec une valeur de propriété pour
Units
,Dalek
est inscrit comme valeur en cascade fixe. - Une deuxième inscription
Dalek
avec une valeur de propriété différente pourUnits
est nommée «AlphaGroup
».
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });
Le composant Daleks
suivant affiche les valeurs en cascade.
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; }
}
Dans l’exemple suivant, Dalek
est inscrit comme valeur en cascade à l’aide de CascadingValueSource<T>
, avec <T>
comme type. L’indicateur isFixed
signale si la valeur est corrigée. Si la valeur est false, tous les destinataires sont abonnés aux notifications de mise à jour émises en appelant NotifyChangedAsync. Les abonnements créent des surcharges et réduisent les performances. Définissez donc isFixed
sur true
, si la valeur ne change pas.
builder.Services.AddCascadingValue(sp =>
{
var dalek = new Dalek { Units = 789 };
var source = new CascadingValueSource<Dalek>(dalek, isFixed: false);
return source;
});
Avertissement
L’inscription d’un type de composant en tant que valeur en cascade de niveau racine ne permet pas d'enregistrer des services supplémentaires pour le type ni d’autoriser l’activation du service dans le composant.
Traitez les services requis séparément des valeurs en cascade, en les inscrivant séparément du type en cascade.
Évitez d’utiliser AddCascadingValue pour inscrire un type de composant en tant que valeur en cascade. Au lieu de cela, enveloppez le <Router>...</Router>
dans le composant Routes
(Components/Routes.razor
) avec le composant et adoptez le rendu interactif global côté serveur (SSR interactif). Pour obtenir un exemple, consultez la section CascadingValue
Composant.
CascadingValue
(composant)
Un composant ancêtre fournit une valeur en cascade à l’aide du composant CascadingValue
du framework Blazor, qui inclut dans un wrapper une sous-arborescence d’une hiérarchie de composants, et fournit une seule valeur à tous les composants de sa sous-arborescence.
L’exemple suivant montre le flux des informations de thème vers le bas de la hiérarchie des composants pour fournir une classe de style CSS aux boutons des composants enfants.
La classe C# ThemeInfo
suivante spécifie les informations relatives au thème.
Remarque
Pour les exemples de cette section, l’espace de noms de l’application est BlazorSample
. Quand vous testez le code dans votre propre exemple d’application, remplacez l’espace de noms de l’application par l’espace de noms de votre exemple d’application.
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; }
}
}
Le composant de disposition suivant spécifie les informations de thème (ThemeInfo
) sous la forme d’une valeur en cascade pour tous les composants qui constituent le corps de disposition de la propriété Body. ButtonClass
se voit affecter la valeur btn-success
, qui est un style de bouton Bootstrap. Tout composant descendant dans la hiérarchie de composants peut utiliser la propriété ButtonClass
via la valeur en cascade 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" };
}
Les Blazor Web App proposent des approches alternatives pour les valeurs en cascade qui s’appliquent plus largement à l’application que la fourniture de ces valeurs via un seul fichier de présentation :
Encapsulez le balisage du composant
Routes
dans un composantCascadingValue
pour spécifier les données comme valeur en cascade pour tous les composants de l’application.L’exemple suivant cascade les données
ThemeInfo
du composantRoutes
.Routes.razor
:<CascadingValue Value="theme"> <Router ...> ... </Router> </CascadingValue> @code { private ThemeInfo theme = new() { ButtonClass = "btn-success" }; }
Remarque
L’intégration de l’instance de composant
Routes
dans le composantApp
(Components/App.razor
) avec un composantCascadingValue
n’est pas pris en charge.Spécifiez une valeur en cascade du niveau racine en tant que service en appelant la méthode d’extension AddCascadingValue sur le générateur de collection de services.
L’exemple suivant cascade les données
ThemeInfo
du fichierProgram
.Program.cs
builder.Services.AddCascadingValue(sp => new ThemeInfo() { ButtonClass = "btn-primary" });
Pour plus d’informations, consultez les sections suivantes de cette article :
Attribut [CascadingParameter]
Pour utiliser des valeurs en cascade, les composants descendants déclarent les paramètres en cascade à l’aide de l’attribut [CascadingParameter]
. Les valeurs en cascade sont liées aux paramètres en cascade par type. La création d’une cascade de plusieurs valeurs du même type est traitée dans la section Créer une cascade de plusieurs valeurs plus loin dans cet article.
Le composant suivant lie la valeur en cascade ThemeInfo
à un paramètre en cascade, en utilisant éventuellement le même nom que ThemeInfo
. Le paramètre permet de définir la classe CSS du bouton 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++;
}
}
À l’image d’un paramètre de composant classique, les composants qui acceptent un paramètre en cascade sont réaffichés quand la valeur en cascade change. Par exemple, la configuration d’une autre instance de thème entraîne un nouveau rendu du composant ThemedCounter
décrit dans la section Composant CascadingValue
.
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 peut être utilisé pour indiquer qu’un paramètre en cascade ne change pas après son initialisation.
Valeurs/paramètres en cascade et limites du mode de rendu
Les paramètres en cascade ne passent pas de données entre les limites du mode de rendu :
Les sessions interactives s’exécutent dans un contexte différent de celui des pages qui utilisent un rendu statique côté serveur (SSR statique). Il n’est pas nécessaire que le serveur produisant la page soit la même machine que celle qui héberge une session ultérieure du serveur interactif, y compris pour les composants WebAssembly où le serveur est une machine différente pour le client. L’avantage du rendu statique côté serveur (SSR statique) est de bénéficier de tous les niveaux de performance du rendu HTML sans état.
L’état qui traverse la limite entre le rendu statique et le rendu interactif doit être sérialisable. Les composants sont des objets arbitraires qui référencent une vaste chaîne d’autres objets, y compris le renderer, le conteneur d’injection de dépendances et chaque instance du service d’injection de dépendances. Vous devez explicitement faire en sorte que l’état soit sérialisé à partir d’un SSR statique pour qu’il soit disponible dans les composants interactifs suivants. Deux approches sont adoptées :
- Avec le framework Blazor, les paramètres transmis d’un SSR statique à une limite de rendu interactif sont sérialisés automatiquement s’ils sont sérialisables en JSON. Sinon, une erreur est renvoyée.
- L’état stocké dans
PersistentComponentState
est sérialisé et récupéré automatiquement s’il est sérialisable en JSON. Sinon, une erreur est renvoyée.
Les paramètres en cascade ne sont pas sérialisables en JSON, car les modèles d’utilisation classiques des paramètres en cascade sont similaires aux services d’injection de dépendance. Il existe souvent des variantes des paramètres en cascade qui sont spécifiques à la plateforme : il serait donc inutile pour les développeurs que le framework les empêche d’avoir des versions spécifiques au serveur interactif ou des versions spécifiques au WebAssembly. En outre, de nombreuses valeurs de paramètres en cascade ne sont généralement pas sérialisables : il serait donc impraticable de mettre à jour des applications existantes si vous deviez arrêter d’utiliser toutes les valeurs de paramètre en cascade non sérialisables.
Recommandations :
Si vous devez rendre l’état disponible pour tous les composants interactifs sous forme de paramètre en cascade, nous vous recommandons d’utiliser des valeurs en cascade au niveau racine. Un modèle de fabrique est disponible et l’application peut émettre des valeurs mises à jour après son démarrage. Les valeurs en cascade au niveau racine sont disponibles pour tous les composants, y compris les composants interactifs, car elles sont traitées en tant que services d’injection de dépendances.
Pour les créateurs de bibliothèques de composants, vous pouvez créer une méthode d’extension pour les consommateurs de bibliothèques, similaire à ceci :
builder.Services.AddLibraryCascadingParameters();
Demandez aux développeurs d’appeler votre méthode d’extension. Cela constitue une bonne alternative à la demande d’ajout d’un composant
<RootComponent>
dans leur composantMainLayout
.
Créer une cascade de plusieurs valeurs
Pour créer une cascade de plusieurs valeurs du même type dans la même sous-arborescence, fournissez une seule chaîne Name à chaque composant CascadingValue
ainsi qu’à leurs attributs [CascadingParameter]
correspondants.
Dans l’exemple suivant, deux composants CascadingValue
font passer en cascade différentes instances de CascadingType
:
<CascadingValue Value="parentCascadeParameter1" Name="CascadeParam1">
<CascadingValue Value="ParentCascadeParameter2" Name="CascadeParam2">
...
</CascadingValue>
</CascadingValue>
@code {
private CascadingType? parentCascadeParameter1;
[Parameter]
public CascadingType? ParentCascadeParameter2 { get; set; }
}
Dans un composant descendant, les paramètres en cascade reçoivent leurs valeurs en cascade du composant ancêtre par Name :
@code {
[CascadingParameter(Name = "CascadeParam1")]
protected CascadingType? ChildCascadeParameter1 { get; set; }
[CascadingParameter(Name = "CascadeParam2")]
protected CascadingType? ChildCascadeParameter2 { get; set; }
}
Passer des données dans une hiérarchie de composants
Les paramètres en cascade permettent également aux composants de passer des données au sein d’une hiérarchie de composants. Prenons l’exemple suivant d’un ensemble d’onglets d’IU, où un composant d’ensemble d’onglets gère une série d’onglets individuels.
Remarque
Pour les exemples de cette section, l’espace de noms de l’application est BlazorSample
. Quand vous testez le code dans votre propre exemple d’application, remplacez l’espace de noms par l’espace de noms de votre exemple d’application.
Créez une interface ITab
que les onglets implémentent dans un dossier nommé UIInterfaces
.
UIInterfaces/ITab.cs
:
using Microsoft.AspNetCore.Components;
namespace BlazorSample.UIInterfaces;
public interface ITab
{
RenderFragment ChildContent { get; }
}
Remarque
Pour plus d’informations sur RenderFragment, consultez Composants ASP.NET Core Razor.
Le composant TabSet
suivant gère un ensemble d’onglets. Les composants Tab
de l’ensemble d’onglets, créés plus loin dans cette section, fournissent les éléments de liste (<li>...</li>
) de la liste (<ul>...</ul>
).
Les composants Tab
enfants ne sont pas explicitement passés en tant que paramètres à TabSet
. À la place, les composants Tab
enfants font partie du contenu enfant de TabSet
. Toutefois, le TabSet
a toujours besoin d’une référence pour chaque composant Tab
afin qu’il puisse afficher les en-têtes et l’onglet actif. Pour activer cette coordination sans nécessiter de code supplémentaire, le TabSet
composant peut servir lui-même de valeur en cascade, qui est ensuite récupérée par les composants Tab
descendants.
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();
}
}
}
Les composants Tab
descendants capturent le TabSet
conteneur en tant que paramètre en cascade. Les composants Tab
s’ajoutent à TabSet
, et se coordonnent pour définir l’onglet actif.
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);
}
}
Le composant ExampleTabSet
suivant utilise le composant TabSet
, qui contient trois composants 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;
}