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.

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; }
}

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 pour Units 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; }
}

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 CascadingValueComposant.

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.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>
    <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 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 composant CascadingValue 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 composant Routes.

    Routes.razor:

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

    Remarque

    L’habillage de l’instance Routes de composant dans le composant App (Components/App.razor) avec un composant CascadingValue 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 fichier Program.

    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"
@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 :

    • Grâce au cadre Blazor, les paramètres transmis d’un SSR statique à une frontière de rendu interactive sont sérialisés automatiquement s’ils sont JSsérialisables, sinon une erreur est généré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 levée.

Les paramètres en cascade ne sont pas JSON-sérialisables, car les modèles d’utilisation classiques des paramètres en cascade sont quelque peu similaires aux services DI. 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 composant MainLayout.

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;
}

Ressources supplémentaires