Dela via


ASP.NET Core Blazor sammanhängande värden och parametrar

Obs

Det här är inte den senaste versionen av den här artikeln. För den aktuella versionen, se .NET 9-versionen av den här artikeln .

Varning

Den här versionen av ASP.NET Core stöds inte längre. Mer information finns i .NET och .NET Core Support Policy. Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Viktig

Den här informationen gäller en förhandsversionsprodukt som kan ändras avsevärt innan den släpps kommersiellt. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

Den aktuella versionen finns i den .NET 9-versionen av den här artikeln.

Den här artikeln beskriver hur du flödar data från en överordnad Razor komponent till underordnade komponenter.

sammanhängande värden och parametrar ger ett bekvämt sätt att flöda data ned i en komponenthierarki från en överordnad komponent till valfritt antal underordnade komponenter. Till skillnad från komponentparametrarkräver sammanhängande värden och parametrar inte någon attributtilldelning för varje underordnad komponent där data används. Sammanhängande värden och parametrar gör det också möjligt för komponenter att samordna med varandra i en komponenthierarki.

Not

Kodexemplen i den här artikeln använder nullbara referenstyper (NRT) och .NET-kompilatorn null-state static analysis, som stöds i ASP.NET Core i .NET 6 eller senare. När du riktar in dig på ASP.NET Core 5.0 eller tidigare tar du bort null-typbeteckningen (?) från CascadingType?, @ActiveTab?, RenderFragment?, ITab?, TabSet?och string? typer i artikelns exempel.

Sammanhängande värden på rotnivå

Sammanhängande värden på rotnivå kan registreras för hela komponenthierarkin. Namngivna sammanhängande värden och prenumerationer för uppdateringsmeddelanden stöds.

Följande klass används i det här avsnittets exempel.

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

Följande registreringar görs i appens Program-fil med AddCascadingValue:

  • Dalek med ett egenskapsvärde för Units registreras som ett fast sammanhängande värde.
  • En andra Dalek registrering med ett annat egenskapsvärde för Units heter "AlphaGroup".
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Följande Daleks komponent visar de överlappande värdena.

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

I följande exempel registreras Dalek som ett sammanhängande värde med hjälp av CascadingValueSource<T>, där <T> är typen. Flaggan isFixed anger om värdet är fast. Om det är falskt prenumererar alla mottagare på uppdateringsmeddelanden, som utfärdas genom att anropa NotifyChangedAsync. Prenumerationer skapar omkostnader och minskar prestanda, så ställ in isFixedtrue om värdet inte ändras.

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

    return source;
});

Varning

Registrering av en komponenttyp som ett sammanhängande värde på rotnivå registrerar inte ytterligare tjänster för typen eller tillåter tjänstaktivering i komponenten.

Behandla nödvändiga tjänster separat från kaskadvärden och registrera dem separat från kaskadtypen.

Undvik att använda AddCascadingValue för att registrera en komponenttyp som ett sammanhängande värde. Omslut i stället <Router>...</Router> i komponenten Routes (Components/Routes.razor) med komponenten och implementera global interaktiv återgivning på serversidan (interaktiv SSR). Ett exempel finns i avsnittet CascadingValue komponent.

CascadingValue komponent

En överordnad komponent ger ett sammanhängande värde med hjälp av Blazor ramverkets CascadingValue komponent, som omsluter ett underträd i en komponenthierarki och tillhandahåller ett enda värde till alla komponenter i dess underträd.

I följande exempel visas flödet av temainformation i komponenthierarkin för att tillhandahålla en CSS-stilklass till knappar i underordnade komponenter.

Följande ThemeInfo C#-klass anger temainformationen.

Not

För exemplen i det här avsnittet är appens namnområde BlazorSample. När du experimenterar med koden i din egen exempelapp ändrar du appens namnområde till exempelappens namnområde.

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

Följande layoutkomponent anger temainformation (ThemeInfo) som ett sammanhängande värde för alla komponenter som utgör layouttexten för egenskapen Body. ButtonClass tilldelas värdet btn-success, vilket är en Bootstrap-knappstil. Alla underordnade komponenter i komponenthierarkin kan använda egenskapen ButtonClass via det ThemeInfo sammanhängande värdet.

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 Appger alternativa metoder för värden i en kaskad som gäller mer allmänt för appen än att tillhandahålla dem via en enda layoutfil.

  • Omslut Routes komponenten i en CascadingValue komponent för att ange data som ett sammanhängande värde för alla appens komponenter.

    Följande exempel överlappar ThemeInfo data från komponenten Routes.

    Routes.razor:

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

    Notis

    Det är inte möjligt att omsluta komponentinstansen Routes i komponenten App (Components/App.razor) med en CascadingValue-komponent.

  • Ange ett sammanhängande värde på rotnivå som en tjänst genom att anropa AddCascadingValue-tilläggsmetoden i servicesamlingsverktyget.

    I följande exempel kaskaderar data från ThemeInfo-filen till Program-filen.

    Program.cs

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

Mer information finns i följande avsnitt i den här artikeln:

[CascadingParameter]-attributet

Om du vill använda sammanhängande värden deklarerar underordnade komponenter sammanhängande parametrar med hjälp av attributet [CascadingParameter]. Sammanhängande värden är bundna till sammanhängande parametrar efter typ. Sammanhängande flera värden av samma typ beskrivs i avsnittet Cascade multiple values senare i den här artikeln.

Följande komponent binder det kaskaderande värdet ThemeInfo till en kaskaderande parameter, valfritt med samma namn som ThemeInfo. Parametern används för att ange CSS-klassen för knappen 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++;
    }
}

På samma sätt som en vanlig komponentparameter återskapas komponenter som accepterar en sammanhängande parameter när det sammanhängande värdet ändras. Om du till exempel konfigurerar en annan temainstans orsakar det att ThemedCounter-komponenten från komponent CascadingValue sektionen återskapas.

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 kan användas för att indikera att en sammanhängande parameter inte ändras efter initieringen.

Sammanhängande värden/parametrar och återgivningslägesgränser

Sammanhängande parametrar skickar inte data över gränserna för återgivningsläge:

  • Interaktiva sessioner körs i en annan kontext än de sidor som använder statisk återgivning på serversidan (statisk SSR). Det finns inget krav på att servern som producerar sidan ens är samma dator som är värd för en senare interaktiv serversession, inklusive för WebAssembly-komponenter där servern är en annan dator än klienten. Fördelen med statisk återgivning på serversidan (statisk SSR) är att få full prestanda för ren tillståndslös HTML-rendering.

  • Tillstånd som korsar gränsen mellan statisk och interaktiv återgivning måste vara serialiserbart. Komponenter är godtyckliga objekt som refererar till en omfattande kedja av andra objekt, inklusive renderaren, DI-containern och varje DI-tjänstinstans. Du måste uttryckligen orsaka att tillståndet serialiseras från statisk SSR för att göra det tillgängligt i efterföljande interaktivt renderade komponenter. Två metoder används:

    • Via det Blazor ramverket serialiseras parametrar som skickas över en statisk SSR till en interaktiv återgivningsgräns automatiskt om de är JSON-serialiserbara eller om ett fel utlöses.
    • Tillståndet som lagras i PersistentComponentState serialiseras och återställs automatiskt om det är JSON-serialiserbart, eller om ett fel utlöses.

Sammanhängande parametrar är inte JSON-serialiserbara eftersom de typiska användningsmönstren för sammanhängande parametrar liknar DI-tjänster. Det finns ofta plattformsspecifika varianter av sammanhängande parametrar, så det skulle inte vara till någon hjälp för utvecklare om ramverket hindrade utvecklare från att ha server-interaktiva specifika versioner eller WebAssembly-specifika versioner. Dessutom är många sammanhängande parametervärden i allmänhet inte serialiserbara, så det skulle vara opraktiskt att uppdatera befintliga appar om du var tvungen att sluta använda alla icke-numeriska sammanhängande parametervärden.

Rekommendationer:

  • Om du behöver göra tillståndet tillgängligt för alla interaktiva komponenter som en sammanhängande parameter rekommenderar vi att du använder sammanhängande värden på rotnivå. Ett fabriksmönster är tillgängligt och appen kan generera uppdaterade värden efter appstart. Sammanhängande värden på rotnivå är tillgängliga för alla komponenter, inklusive interaktiva komponenter, eftersom de bearbetas som DI-tjänster.

  • För komponentbiblioteksförfattare kan du skapa en tilläggsmetod för bibliotekskonsumenter som liknar följande:

    builder.Services.AddLibraryCascadingParameters();
    

    Instruera utvecklare att anropa tilläggsmetoden. Det här är ett bra alternativ till att instruera dem att lägga till en <RootComponent> komponent i sin MainLayout komponent.

Kaskadera flera värden

Om du vill överlappa flera värden av samma typ i samma underträd anger du en unik Name sträng för varje CascadingValue komponent och motsvarande [CascadingParameter] attribut.

I följande exempel överlappar två CascadingValue komponenter olika instanser av CascadingType:

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

@code {
    private CascadingType? parentCascadeParameter1;

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

I en underordnad komponent tar de kaskadparametrarna emot sina kaskadvärden från den överordnade komponenten via Name:

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

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

Skicka data över en komponenthierarki

Sammanhängande parametrar gör det också möjligt för komponenter att skicka data över en komponenthierarki. Överväg följande exempel på flikuppsättningen för användargränssnittet, där en flikuppsättningskomponent underhåller en serie enskilda flikar.

Obs

För exemplen i det här avsnittet är appens namnområde BlazorSample. När du experimenterar med koden i din egen exempelapp ändrar du namnområdet till exempelappens namnområde.

Skapa ett ITab gränssnitt som flikar implementerar i en mapp med namnet UIInterfaces.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Not

Mer information om RenderFragmentfinns i ASP.NET Core Razor-komponenter.

Den följande TabSet-komponenten håller en uppsättning flikar. Tabbuppsättningens Tab komponenter, som skapas senare i det här avsnittet, anger listobjekten (<li>...</li>) för listan (<ul>...</ul>).

Underordnade Tab-komponenter passeras inte uttryckligen som parametrar till TabSet. I stället är komponenterna i Tab en del av innehållet för TabSet. Men TabSet behöver fortfarande en referens varje Tab komponent så att den kan återge rubrikerna och den aktiva fliken. Om du vill aktivera den här samordningen utan att behöva ytterligare kod kan TabSet komponenten ange sig själv som ett sammanhängande värde som sedan hämtas av de underordnade Tab komponenterna.

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

Underordnade Tab komponenter avbildar de innehållande TabSet som en sammanhängande parameter. De Tab komponenterna lägger till sig själva i TabSet och samordnar för att ange den aktiva fliken.

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

Följande ExampleTabSet komponent använder komponenten TabSet, som innehåller tre Tab komponenter.

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

Ytterligare resurser