Compartir a través de


Valores y parámetros en cascada de Blazor en ASP.NET Core

Nota

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Advertencia

Esta versión de ASP.NET Core ya no se admite. Para obtener más información, consulte la directiva de compatibilidad de .NET y .NET Core. Para la versión actual, consulte la versión de .NET 9 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión de .NET 9 de este artículo.

Obtén información sobre cómo transferir datos desde un componente Razor antecesor a componentes descendientes.

Los valores y parámetros en cascada proporcionan una manera de conveniente de hacer fluir los datos por una jerarquía de componentes desde un componente antecesor a cualquier número de componentes descendiente. A diferencia de los parámetros de componente, los valores y parámetros en cascada no requieren una asignación de atributo para cada componente descendiente en el que se consuman los datos. Los valores y parámetros en cascada también permiten que los componentes se coordinen entre sí en una jerarquía de componentes.

Nota

En los ejemplos de código de este artículo se adoptan tipos de referencia que admiten un valor NULL (NRT) y análisis estático de estado NULL del compilador de .NET, que se admiten en ASP.NET Core en .NET 6 o posterior. Al dirigirse a .NET 5 o versiones anteriores, quite la designación de tipo null (?) de los CascadingType?, @ActiveTab?, RenderFragment?, ITab?, TabSet? y string? de los ejemplos del artículo.

Valores en cascada de nivel raíz

Los valores en cascada de nivel raíz se pueden registrar para toda la jerarquía de componentes. Se admiten los valores en cascada con nombre y las suscripciones para las notificaciones de actualización.

En los ejemplos de esta sección se usa la siguiente clase.

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

Los registros siguientes se realizan en el archivo Program de la aplicación con AddCascadingValue:

  • Dalek con un valor de propiedad para Units se registra como un valor fijo en cascada.
  • Un segundo registro Dalek con un valor de propiedad diferente para Units se denomina "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

En el componente Daleks siguiente se muestran los valores en cascada.

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

En el ejemplo siguiente, Dalek se registra como un valor en cascada mediante CascadingValueSource<T>, donde <T> es el tipo. La marca isFixed indica si el valor es fijo. Si false, todos los destinatarios están suscritos a las notificaciones de actualización. Las suscripciones crean sobrecarga y reducen el rendimiento, por lo que se establece isFixed en true si el valor no cambia.

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

    return source;
});

Advertencia

El registro de un tipo de componente como un valor en cascada de nivel raíz no registra servicios adicionales para el tipo ni permite la activación del servicio en el componente.

Trata los servicios necesarios por separado de los valores en cascada, registrándolos por separado del tipo en cascada.

Evita usar AddCascadingValue para registrar un tipo de componente como un valor en cascada. En su lugar, encapsula el <Router>...</Router> en el componente Routes (Components/Routes.razor) con el componente y adopta la representación global interactiva del lado servidor (SSR interactivo). Para obtener un ejemplo, consulta la sección componente CascadingValue.

Valores en cascada de nivel raíz con notificaciones

Llamar a NotifyChangedAsync para enviar notificaciones de actualización puede usarse para indicar a varios suscriptores de la componente Razor que un valor en cascada ha cambiado. Las notificaciones no son posibles para los suscriptores que adoptan la representación estática del lado servidor (SSR estático), por lo que los suscriptores deben adoptar un modo de representación interactivo.

En el ejemplo siguiente:

  • NotifyingDalek implementa INotifyPropertyChanged para notificar a los clientes que ha cambiado un valor de propiedad. Cuando se establece la propiedad Units, se invoca PropertyChangedEventHandler (PropertyChanged).
  • El método SetUnitsToOneThousandAsync puede ser activado por los suscriptores para establecer Units en 1.000 con un retraso de procesamiento simulado.

Tenga en cuenta para el código de producción que cualquier cambio en el estado (cualquier cambio de valor de propiedad de la clase) hace que todos los componentes suscritos se rendericen de nuevo, sin importar qué parte del estado utilicen. Se recomienda crear clases granulares y gestionarlas por separado en cascada con suscripciones específicas para asegurarse de que solo los componentes suscritos a una parte concreta del estado de la aplicación sean afectados por los cambios.

Nota

Para una Blazor Web App solución que consta de proyectos de servidor y cliente (.Client), el siguiente NotifyingDalek.cs archivo se coloca en el .Client proyecto.

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

A continuación CascadingStateServiceCollectionExtensions crea un CascadingValueSource<TValue> a partir de un tipo que implementa INotifyPropertyChanged.

Nota

Para una Blazor Web App solución que consta de proyectos de servidor y cliente (.Client), el siguiente CascadingStateServiceCollectionExtensions.cs archivo se coloca en el .Client proyecto.

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

El tipo PropertyChangedEventHandler (HandlePropertyChanged) llama al método CascadingValueSource<TValue> de NotifyChangedAsync para notificar a los suscriptores que el valor en cascada ha cambiado. Task se descarta al llamar a NotifyChangedAsync, porque la llamada solo representa la duración del despacho al contexto sincrónico. Las excepciones se gestionan internamente enviándolas al renderizador en el contexto del componente que lanzó la excepción al recibir la actualización. Esta es la misma manera en que las excepciones se procesan con CascadingValue<TValue>, que no son notificadas sobre las excepciones que ocurren dentro de los destinatarios de la notificación. El controlador de eventos está desconectado en el Dispose método para evitar una pérdida de memoria.

En el Program archivo, NotifyingDalek se pasa para crear un CascadingValueSource<TValue> con un valor inicial Unit de 888 unidades:

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

Nota

Para una Blazor Web App solución que consta de proyectos de servidor y cliente (.Client), se coloca el código anterior en el archivo Program de cada proyecto.

El siguiente componente se usa para demostrar cómo cambiar el valor de NotifyingDalek.Units notifica a los suscriptores.

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

Para mostrar varias notificaciones de suscriptor, el componente siguiente DaleksMain representa tres Daleks componentes. Cuando se actualiza el recuento de unidades (Units) de un Dalek componente, se actualizan los otros dos Dalek suscriptores de componentes.

DaleksMain.razor:

@page "/daleks-main"

<PageTitle>Daleks Main</PageTitle>

<h1>Daleks Main</h1>

<Daleks />

<Daleks />

<Daleks />

Agregue un enlace de navegación al componente DaleksMain en 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>

Dado que el CascadingValueSource<TValue>tipo de este ejemplo (NotifyingDalek) es un tipo de clase, puede cumplir prácticamente cualquier requisito de especificación de características de administración de estado. Sin embargo, las suscripciones crean sobrecarga y reducen el rendimiento, por lo que comparan el rendimiento de este enfoque en la aplicación y lo comparan con otros enfoques de administración de estado antes de adoptarlo en una aplicación de producción con recursos de memoria y procesamiento restringidos.

Cualquier cambio en el estado (cualquier cambio en el valor de propiedad de la clase) provoca que todos los componentes suscritos realicen un renderizado de nuevo, independientemente de la parte del estado que utilicen. Evite crear una sola clase grande que represente todo el estado de la aplicación global. En su lugar, cree clases granulares y en cascada por separado con suscripciones específicas a parámetros en cascada, lo que garantiza que solo los componentes suscritos a una parte específica del estado de la aplicación se vean afectados por los cambios.

Componente CascadingValue

Un componente antecesor proporciona un valor en cascada mediante el componente Blazor del marco CascadingValue, que ajusta un subárbol de una jerarquía de componentes y proporciona un valor único a todos los componentes de su subárbol.

El siguiente ejemplo demuestra el flujo de información del tema en la jerarquía de componentes para proporcionar una clase de estilo CSS a los botones de los componentes secundarios.

La siguiente clase de C# ThemeInfo especifica la información del tema.

Nota

En los ejemplos de esta sección, el espacio de nombres de la aplicación es BlazorSample. Cuando experimentes con el código de la aplicación de ejemplo, cambia el espacio de nombres de la aplicación por el de la aplicación de ejemplo.

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

El componente de diseño siguiente especifica información de tema (ThemeInfo) como un valor en cascada para todos los componentes que constituyen el cuerpo del diseño de la propiedad Body. Se asigna a ButtonClass un valor de btn-success, que es un estilo de botón de arranque. Cualquier componente descendiente de la jerarquía de componentes puede usar la propiedad ButtonClass a través del valor en cascada 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" };
}

Las Blazor Web App proporcionan enfoques alternativos para los valores en cascada que se aplican de forma más amplia a la aplicación que proporcionarlos a través de un archivo de diseño único:

  • Encapsula el marcado del componente de Routes en un componente de CascadingValue para especificar los datos como un valor en cascada para todos los componentes de la aplicación.

    En el siguiente ejemplo se muestran los datos ThemeInfo en cascada del componente Routes.

    Routes.razor:

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

    Nota

    Ajustar el componente de la instancia Routes en el componente App (Components/App.razor) con un componente CascadingValue no se admite.

  • Especifica un valor en cascada de nivel raíz como servicio llamando al método de extensión AddCascadingValue en el generador de colecciones de servicios.

    En el siguiente ejemplo se muestran los datos ThemeInfo en cascada del archivoProgram.

    Program.cs

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

Para obtener más información, consulta las siguientes secciones de este artículo:

Atributo [CascadingParameter]

Para usar valores en cascada, los componentes descendientes declaran parámetros en cascada mediante el atributo [CascadingParameter]. Los valores en cascada se enlazan a los parámetros en cascada según el tipo. Más adelante en este artículo, en la sección Valores múltiples en cascada, se describe cómo se ponen en cascada varios valores del mismo tipo.

El componente siguiente enlaza el valor en cascada ThemeInfo a un parámetro en cascada y, opcionalmente, usa el mismo nombre de ThemeInfo. El parámetro se usa para establecer la clase CSS para el botón 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++;
    }
}

De forma similar a un parámetro de componente normal, los componentes que aceptan un parámetro en cascada se reprocesan cuando se cambia el valor en cascada. Por ejemplo, la configuración de una instancia de tema diferente hace que se muestre el componente ThemedCounter de la sección CascadingValue componente.

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 se puede usar para indicar que un parámetro en cascada no cambia después de la inicialización.

Valores o parámetros en cascada y límites de modo de representación

Los parámetros en cascada no pasan datos a través de los límites del modo de representación:

  • Las sesiones interactivas se ejecutan en un contexto diferente a las páginas que usan la representación estática del lado servidor (SSR estático). No es necesario que el servidor que produce la página sea incluso la misma máquina que hospeda una sesión de servidor interactivo posterior, incluidos los componentes de WebAssembly donde el servidor es una máquina diferente al cliente. La ventaja de la representación de servidor estático es obtener el rendimiento completo de la representación HTML sin estado pura.

  • El estado que cruza el límite entre la representación estática e interactiva debe ser serializable. Los componentes son objetos arbitrarios que hacen referencia a una gran cadena de otros objetos, como el representador, el contenedor de di y todas las instancias del servicio de inserción de dependencias. Debes hacer que el estado se serialice explícitamente desde la representación del servidor estático para que esté disponible en componentes representados interactivamente posteriores. Se adoptan dos enfoques:

    • A través del marco de Blazor, los parámetros pasados a través de una representación de servidor estático al límite interactivo se serializan automáticamente si son serializables en JSON o se genera un error.
    • El estado almacenado en PersistentComponentState se serializa y se recupera automáticamente si es serializable en JSON o se genera un error.

Los parámetros en cascada no son serializables en JSON porque los patrones de uso típicos de los parámetros en cascada son algo así como los servicios DI. A menudo hay variantes específicas de la plataforma de parámetros en cascada, por lo que sería útil para los desarrolladores si el marco de trabajo detuvo a los desarrolladores de tener versiones específicas de servidor interactivas o versiones específicas de WebAssembly. Además, muchos valores de parámetro en cascada en general no son serializables, por lo que sería poco práctico actualizar las aplicaciones existentes si tuviera que dejar de usar todos los valores de parámetros en cascada no serializables.

Recomendaciones:

  • Si necesita poner el estado a disposición de todos los componentes interactivos como parámetro en cascada, se recomienda usar valores en cascada de nivel raíz o valores en cascada de nivel raíz con notificaciones. Hay disponible un patrón de fábrica y la aplicación puede emitir valores actualizados después del inicio de la aplicación. Los valores en cascada de nivel raíz están disponibles para todos los componentes, incluidos los componentes interactivos, ya que se procesan como servicios de inserción de dependencias.

  • En el caso de los autores de bibliotecas de componentes, puedes crear un método de extensión para los consumidores de bibliotecas similares a los siguientes:

    builder.Services.AddLibraryCascadingParameters();
    

    Indica a los desarrolladores que llamen al método de extensión. Se trata de una alternativa de sonido para indicarles que agreguen un componente de <RootComponent> en su componente de MainLayout.

Valores múltiples en cascada

Para poner en cascada varios valores del mismo tipo en un mismo subárbol, proporciona una cadena Name única en cada componente CascadingValue y sus correspondientes atributos [CascadingParameter].

En el siguiente ejemplo, dos componentes CascadingValue ponen en cascada instancias distintas de CascadingType:

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

@code {
    private CascadingType? parentCascadeParameter1;

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

En un componente descendiente, los parámetros en cascada reciben sus valores en cascada del componente antecesor en función de Name:

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

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

Paso de datos en una jerarquía de componentes

Los parámetros en cascada también permiten que los componentes pasen datos en jerarquía de componentes. Considera el siguiente ejemplo de conjunto de pestañas de interfaz de usuario, donde un componente del conjunto de pestañas mantiene una serie de pestañas individuales.

Nota

En los ejemplos de esta sección, el espacio de nombres de la aplicación es BlazorSample. Cuando experimentes con el código de la aplicación de ejemplo, cambia el espacio de nombres por el de la aplicación de ejemplo.

Crea una interfaz ITab que las pestañas implementen en una carpeta denominada UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Nota

Para obtener más información sobre RenderFragment, consulta Componentes Razor de ASP.NET Core.

El componente TabSet siguiente mantiene un conjunto de pestañas. Los componentes Tab del conjunto de pestañas, que se crean más adelante en esta sección, proporcionan los elementos de lista (<li>...</li>) para la lista (<ul>...</ul>).

Los componentes Tab secundarios no se pasan explícitamente como parámetros a TabSet, sino que forman parte del contenido secundario de Tab. Sin embargo, el TabSet todavía necesita una referencia a cada componente de Tab para que pueda representar los encabezados y la pestaña activa. Para habilitar esta coordinación sin necesidad de código adicional, el componente TabSetpuede proporcionarse como un valor en cascada que, a continuación, los componentes de Tab descendientes seleccionan.

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

Los componentes Tab descendientes capturan el elemento TabSet contenedor como parámetro en cascada. Los componentes Tab se agregan a sí mismos al elemento TabSet y se coordinan para establecer la pestaña activa.

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

El componente ExampleTabSet siguiente usa el componente TabSet, que contiene tres componentes 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;
}

Recursos adicionales