ASP.NET Core Blazor valori e parametri a catena

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

Avviso

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto per .NET e .NET Core. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

Questo articolo illustra come trasmettere i dati da un componente antenato Razor ai componenti discendenti.

Valori e parametri in cascata offrono un modo pratico per eseguire il flusso dei dati in una gerarchia di componenti da un componente antenato a un numero qualsiasi di componenti discendenti. A differenza dei parametri del componente, i valori e i parametri a catena non richiedono un'assegnazione di attributo per ogni componente discendente in cui vengono utilizzati i dati. I valori e i parametri a cascata consentono anche ai componenti di coordinarsi tra loro in una gerarchia di componenti.

Nota

Gli esempi di codice in questo articolo adottano tipi di riferimento nullable (NRT) e l'analisi statica dello stato null del compilatore .NET, supportati in ASP.NET Core in .NET 6 o versione successiva. Quando si prende di mira .NET 5 o versioni precedenti, rimuovere la designazione di tipo null (?) dai tipi CascadingType?, @ActiveTab?, RenderFragment?, ITab?, TabSet? e string? negli esempi dell'articolo.

Valori a cascata radice

I valori a cascata a livello radice possono essere registrati per l'intera gerarchia dei componenti. Sono supportati valori a cascata denominati e sottoscrizioni per le notifiche di aggiornamento.

La classe seguente viene usata negli esempi di questa sezione.

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

Le registrazioni seguenti vengono effettuate nel file dell'app Program con AddCascadingValue:

  • Dalek con un valore della proprietà per Units viene registrato come valore a cascata fisso.
  • Una seconda Dalek registrazione con un valore di proprietà diverso per Units è denominata "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Nel componente seguente Daleks vengono visualizzati i valori a cascata.

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]
    private 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]
    private Dalek? Dalek { get; set; }

    [CascadingParameter(Name = "AlphaGroup")]
    public Dalek? AlphaGroupDalek { get; set; }
}

Nell'esempio seguente, Dalek viene registrato come valore a cascata usando CascadingValueSource<T>, dove <T> è il tipo. Il isFixed flag indica se il valore è fisso. Se false, tutti i destinatari vengono sottoscritti per le notifiche di aggiornamento. Le sottoscrizioni creano overhead e riducono le prestazioni, quindi impostate isFixed su true se il valore non cambia.

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

    return source;
});

Avviso

La registrazione di un tipo di componente come valore a cascata a livello radice non registra servizi aggiuntivi per il tipo né consente l'attivazione del servizio nel componente.

Trattare i servizi necessari separatamente dai valori in cascata, registrandoli separatamente dal tipo in cascata.

Evitare di usare AddCascadingValue per registrare un tipo di componente come valore a cascata. Invece, racchiudi <Router>...</Router> nel componente Routes (Components/Routes.razor) con il componente e adotta il rendering interattivo globale lato server (SSR interattivo). Per esempio, vedere la sezione del CascadingValue componente.

Valori a cascata a livello radice con notifiche

La chiamata NotifyChangedAsync a inviare notifiche di aggiornamento può essere usata per segnalare a più Razor sottoscrittori di componenti che un valore a catena è cambiato. Le notifiche non sono possibili per i sottoscrittori che adottano il rendering statico lato server (SSR statico), quindi i sottoscrittori devono adottare una modalità di rendering interattiva.

Nell'esempio seguente:

  • NotifyingDalek implementa INotifyPropertyChanged per notificare ai client che un valore della proprietà è stato modificato. Quando la Units proprietà è impostata, il PropertyChangedEventHandler (PropertyChanged) viene richiamato.
  • Il SetUnitsToOneThousandAsync metodo può essere attivato dai sottoscrittori per impostare Units su 1.000 con un ritardo di elaborazione simulato.

Tenere presente nel codice di produzione che qualsiasi modifica dello stato (qualsiasi modifica del valore della proprietà della classe) causa il ri-rendering di tutti i componenti sottoscritti, indipendentemente da quale parte dello stato venga utilizzata. È consigliabile creare classi granulari, che vengono propagate separatamente con sottoscrizioni specifiche per garantire che solo i componenti sottoscritti a una parte specifica dello stato dell'applicazione siano interessati dalle modifiche.

Nota

Per una Blazor Web App soluzione costituita da progetti server e client (.Client), il file seguente NotifyingDalek.cs viene inserito nel .Client progetto.

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

Il seguente CascadingStateServiceCollectionExtensions crea un CascadingValueSource<TValue> da un tipo che implementa INotifyPropertyChanged.

Nota

Per una Blazor Web App soluzione costituita da progetti server e client (.Client), il file seguente CascadingStateServiceCollectionExtensions.cs viene inserito nel .Client progetto.

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

Il tipo PropertyChangedEventHandler (HandlePropertyChanged) chiama il metodo di CascadingValueSource<TValue>NotifyChangedAsync per notificare ai sottoscrittori che il valore a cascata è stato modificato. L'oggetto Task viene rimosso quando si chiama NotifyChangedAsync perché la chiamata rappresenta solo la durata dell'invio al contesto sincrono. Le eccezioni vengono gestite internamente indirizzandole al renderer nel contesto del componente che ha generato l'eccezione durante la ricezione dell'aggiornamento. Si tratta dello stesso modo in cui le eccezioni vengono elaborate con un CascadingValue<TValue>oggetto , che non riceve notifiche sulle eccezioni che si verificano all'interno dei destinatari delle notifiche. Il gestore eventi viene disconnesso nel Dispose metodo per evitare una perdita di memoria.

Nel file Program, NotifyingDalek viene passato per creare un CascadingValueSource<TValue> con un valore iniziale Unit di 888 unità.

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

Nota

Per una Blazor Web App soluzione costituita da progetti server e client (.Client), il codice sopra menzionato viene inserito nel file Program di ciascun progetto.

Il componente seguente viene usato per illustrare come modificare il valore di NotifyingDalek.Units notifica ai sottoscrittori.

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

Per illustrare più notifiche degli abbonati, il componente seguente DaleksMain renderizza tre Daleks componenti. Quando viene aggiornato il numero di unità (Units) di un Dalek componente, gli altri due Dalek sottoscrittori dei componenti vengono aggiornati.

DaleksMain.razor:

@page "/daleks-main"

<PageTitle>Daleks Main</PageTitle>

<h1>Daleks Main</h1>

<Daleks />

<Daleks />

<Daleks />

Aggiungere un collegamento di navigazione al componente DaleksMain in 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>

Poiché in questo esempio il tipo di CascadingValueSource<TValue> (NotifyingDalek) è un tipo di classe, è possibile soddisfare praticamente qualsiasi requisito della specifica delle funzionalità di gestione dello stato. Tuttavia, le sottoscrizioni creano overhead e riducono le prestazioni, quindi confrontano le prestazioni di questo approccio nella tua app e confrontalo con altri approcci di gestione dello stato prima di adottarlo in un'app di produzione con risorse di elaborazione e memoria limitate.

Qualsiasi modifica dello stato (qualsiasi variazione del valore di una proprietà della classe) fa sì che tutti i componenti registrati vengano rindirizzati, indipendentemente dalla parte dello stato utilizzata. Evitare di creare una singola classe di grandi dimensioni che rappresenta l'intero stato dell'applicazione globale. Creare invece classi granulari e propagarle separatamente con sottoscrizioni specifiche ai parametri a catena, assicurandosi che solo i componenti sottoscritti a una parte specifica dello stato dell'applicazione siano interessati dalle modifiche.

CascadingValue componente

Un componente antenato fornisce un valore a cascata usando il componente CascadingValue del framework Blazor, che racchiude un sottoalbero di una gerarchia di componenti e fornisce un singolo valore a tutti i componenti all'interno del relativo sottoalbero.

Nell'esempio seguente viene illustrato il flusso delle informazioni sul tema nella gerarchia dei componenti per fornire una classe di stile CSS ai pulsanti nei componenti figlio.

La classe C# seguente ThemeInfo specifica le informazioni sul tema.

Nota

Per gli esempi in questa sezione, lo spazio dei nomi dell'app è BlazorSample. Quando si sperimenta il codice nella propria app di esempio, modificare il namespace dell'app nel namespace della propria app di esempio.

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

Il componente di layout seguente specifica le informazioni sul tema (ThemeInfo) come valore in cascata per tutti i componenti che costituiscono il corpo del layout della proprietà Body. ButtonClass viene assegnato un valore di btn-success, che è uno stile pulsante Bootstrap. Qualsiasi componente discendente nella gerarchia dei componenti può usare la ButtonClass proprietà tramite il ThemeInfo valore a cascata.

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 forniscono approcci alternativi per i valori a cascata che si applicano più ampiamente all'app rispetto al fornirli tramite un unico file di layout:

  • Racchiudi il markup del componente Routes in un componente CascadingValue per specificare i dati come valore a cascata per tutti i componenti dell'app.

    Nell'esempio seguente vengono propagati i ThemeInfo dati dal Routes componente .

    Routes.razor:

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

    Nota

    Il wrapping dell'istanza del componente Routes nel componente App (Components/App.razor) con un componente CascadingValue non è supportato.

  • Specificare un valore a catena a livello radice come servizio chiamando il AddCascadingValue metodo di estensione sul generatore della raccolta di servizi.

    Nell'esempio seguente vengono propagati i ThemeInfo dati dal Program file .

    Program.cs

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

Per altre informazioni, vedere le sezioni seguenti di questo articolo:

Attributo [CascadingParameter]

Per usare i valori a cascata, i componenti figlio dichiarano parametri a cascata usando l'attributo [CascadingParameter]. I valori a catena sono associati ai parametri a catena per tipo. La propagazione di più valori dello stesso tipo è descritta nella sezione Cascade multiple values più avanti in questo articolo.

Il private modificatore di accesso è consigliato per i parametri a catena perché il parametro deve essere usato solo all'interno della classe del componente nella maggior parte dei casi. Quando è necessaria la sottoclasse, usare il protected modificatore di accesso.

Il componente seguente associa il valore a cascata ThemeInfo a un parametro a cascata, facoltativamente usando lo stesso nome di ThemeInfo. Il parametro viene usato per impostare la classe CSS per il Increment Counter (Themed) pulsante.

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]
    private 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]
    private 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]
    private 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]
    private 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]
    private 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]
    private ThemeInfo ThemeInfo { get; set; }

    private void IncrementCount()
    {
        currentCount++;
    }
}

Analogamente a un parametro di componente normale, i componenti che accettano un parametro a cascata vengono rirenderati quando viene modificato il valore in cascata. Ad esempio, la configurazione di un'istanza del tema diversa causa un nuovo rendering del componente ThemedCounter dalla sezione del componente CascadingValue.

MainLayout.razor:

<main>
    <div class="top-row px-4">
        <a href="https://learn.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 può essere usato per indicare che un parametro a cascata non cambia dopo l'inizializzazione.

Valori/parametri a cascata e limiti della modalità di rendering

I parametri a catena non passano i dati tra i confini delle modalità di rendering:

  • Le sessioni interattive vengono eseguite in un contesto diverso rispetto alle pagine che usano il rendering statico lato server (SSR statico). Non è necessario che il server che produce la pagina sia anche lo stesso computer che ospita alcune sessioni successive di Interactive Server, anche nel caso dei componenti WebAssembly in cui il server è un computer diverso dal client. Il vantaggio del rendering statico lato server (SSR statico) è quello di ottenere le prestazioni complete del rendering HTML senza stato puro.

  • Lo stato che attraversa il confine tra il rendering statico e interattivo deve essere serializzabile. I componenti sono oggetti arbitrari che fanno riferimento a una vasta catena di altri oggetti, tra cui il renderer, il contenitore DI e ogni istanza di servizio DI. È necessario fare in modo esplicito che lo stato venga serializzato da SSR statico per renderlo disponibile nei componenti visualizzati in modo interattivo successivo. Vengono adottati due approcci:

    • Tramite il Blazor framework, i parametri passati attraverso il confine tra SSR statico e rendering interattivo vengono serializzati automaticamente se sono serializzabili in JSON, oppure viene generato un errore.
    • Lo stato archiviato in Persistent Component State viene serializzato e ripristinato automaticamente se è serializzabile in JSON o viene generato un errore.

I parametri a cascata non sono serializzabili in JSON perché i modelli di utilizzo tipici per i parametri a cascata sono in qualche modo simili ai servizi di dependency injection. Spesso ci sono varianti specifiche della piattaforma di parametri a cascata, quindi sarebbe inutile per gli sviluppatori se il framework impedisse agli sviluppatori di avere versioni specifiche per server interattivo o versioni specifiche di WebAssembly. Inoltre, molti valori dei parametri a cascata in generale non sono serializzabili, quindi sarebbe poco pratico aggiornare le app esistenti se si dovesse interrompere l'uso di tutti i valori dei parametri a cascata non serializzabili.

Raccomandazioni:

  • Se è necessario rendere disponibile lo stato per tutti i componenti interattivi come parametro a catena, è consigliabile usare valori a catena a livello radice o valori a catena a livello radice con notifiche. È disponibile un factory pattern e l'app può generare valori aggiornati dopo l'avvio dell'app. I valori a cascata a livello radice sono disponibili per tutti i componenti, inclusi i componenti interattivi, poiché vengono elaborati come servizi di dependency injection.

  • Per gli autori di librerie di componenti, è possibile creare un metodo di estensione per i consumer di libreria in modo simile al seguente:

    builder.Services.AddLibraryCascadingParameters();
    

    Istruire gli sviluppatori a chiamare il metodo di estensione. Si tratta di una valida alternativa per indicare loro di aggiungere un componente <RootComponent> nel loro componente MainLayout.

Disporre più valori a cascata

Per propagare più valori dello stesso tipo all'interno dello stesso sottoalbero, specificare una stringa univoca Name per ogni componente CascadingValue e i corrispondenti [CascadingParameter] attributi.

Nell'esempio seguente due CascadingValue componenti applicano in cascata istanze diverse di CascadingType:

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

@code {
    private CascadingType? parentCascadeParameter1;

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

In un componente discendente, i parametri in cascata ricevono i valori in cascata dal componente antenato da Name:

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

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

Passare i dati attraverso una gerarchia di componenti

I parametri a cascata consentono anche ai componenti di passare i dati attraverso una gerarchia di componenti. Si consideri l'esempio di set di schede dell'interfaccia utente seguente, in cui un componente del set di schede gestisce una serie di singole schede.

Nota

Per gli esempi in questa sezione, lo spazio dei nomi dell'app è BlazorSample. Quando si sperimenta il codice nella propria app di esempio, modificare lo spazio dei nomi con lo spazio dei nomi della propria app di esempio.

Creare un'interfaccia ITab implementata da tab in una cartella denominata UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Nota

Per altre informazioni su RenderFragment, vedere componenti di ASP.NET CoreRazor.

Il componente seguente TabSet gestisce un set di schede. I componenti del set di schede Tab, creati più avanti in questa sezione, forniscono gli elementi dell'elenco (<li>...</li>) per l'elenco (<ul>...</ul>).

I componenti figlio Tab non vengono passati in modo esplicito come parametri al TabSet. I componenti figlio Tab fanno invece parte del contenuto figlio di TabSet. Tuttavia, l'TabSet necessita ancora di un riferimento a ogni componente Tab in modo che possa eseguire il rendering delle intestazioni e della scheda attiva. Per abilitare questo coordinamento senza richiedere codice aggiuntivo, il componente TabSetpuò fornire se stesso come valore a catena che viene quindi prelevato dai componenti Tab discendenti.

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

I componenti discendenti Tab ricevono l'elemento contenitore TabSet come parametro a catena (cascading). I Tab componenti si aggiungono alla TabSet e si coordinano per impostare la scheda attiva.

Tab.razor:

@using BlazorSample.UIInterfaces
@implements ITab

<li>
    <a @onclick="ActivateTab" class="nav-link @TitleCssClass" role="button">
        @Title
    </a>
</li>

@code {
    [CascadingParameter]
    private 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);
    }
}

Il componente seguente ExampleTabSet usa il TabSet componente , che contiene tre Tab componenti.

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