Kaskadierende Werte und Parameter in ASP.NET Core Blazor

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

Wichtig

Diese Informationen beziehen sich auf ein Vorabversionsprodukt, das vor der kommerziellen Freigabe möglicherweise noch wesentlichen Änderungen unterliegt. Microsoft gibt keine Garantie, weder ausdrücklich noch impliziert, hinsichtlich der hier bereitgestellten Informationen.

Informationen zum aktuellen Release finden Sie in der .NET 8-Version dieses Artikels.

In diesem Artikel wird erläutert, wie Sie Daten aus einer Razor-Vorgängerkomponente an Nachfolgerkomponenten übertragen.

Kaskadierende Werte und Parameter bieten eine praktische Möglichkeit, Daten nach unten in einer Komponentenhierarchie zu übermitteln, dabei kann es sich um eine Vorgängerkomponente bis hin zu beliebigen untergeordneten Nachfolgerkomponenten handeln. Im Gegensatz zu Komponentenparametern erfordern kaskadierende Werte und Parameter keine Attributzuweisung für jede Nachfolgerkomponente, in der die Daten genutzt werden. Mithilfe von kaskadierenden Werten und Parametern können Komponenten auch über eine Komponentenhierarchie miteinander abgestimmt werden.

Hinweis

Die Codebeispiele in diesem Artikel verwenden Nullwerte zulassende Verweistypen (Nullable Reference Types, NRTs) und die statische Analyse des NULL-Zustands des .NET-Compilers, die in ASP.NET Core in .NET 6 oder höher unterstützt werden. Entfernen Sie bei ASP.NET Core 5.0 oder früher die NULL-Typbezeichnung (?) aus den Typen CascadingType?, @ActiveTab?, RenderFragment?, ITab?, TabSet? und string? in den Beispielen des Artikels.

Kaskadierende Werte auf Stammebene

Kaskadierende Werte auf Stammebene können für die gesamte Komponentenhierarchie registriert werden. Benannte kaskadierende Werte und Abonnements für Updatebenachrichtigungen werden unterstützt.

In den Beispielen dieses Abschnitts wird die folgende Klasse verwendet.

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

In der Program-Datei der App werden mit AddCascadingValue folgende Registrierungen vorgenommen:

  • Dalek mit einem Eigenschaftswert für Units wird als fester Kaskadierungswert registriert.
  • Eine zweite Dalek-Registrierung mit einem anderen Eigenschaftswert für Units wird AlphaGroup benannt.
builder.Services.AddCascadingValue(sp => new Dalek { Units = 123 });
builder.Services.AddCascadingValue("AlphaGroup", sp => new Dalek { Units = 456 });

Die folgende Daleks-Komponente zeigt die kaskadierten Werte an.

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

Im folgenden Beispiel wird Dalek mithilfe von CascadingValueSource<T> als kaskadierender Wert registriert, wobei <T> der Typ ist. Das Flag isFixed gibt an, ob der Wert festgelegt ist. Bei FALSE werden Updatebenachrichtigungen, die durch Aufrufen von NotifyChangedAsync ausgegeben werden, für alle Empfänger abonniert. Abonnements erzeugen einen Overhead und verringern die Leistung. Legen Sie isFixed daher auf true fest, wenn sich der Wert nicht ändert.

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

Warnung

Durch die Registrierung eines Komponententyps als überlappenden Wert auf Stammebene werden keine zusätzlichen Dienste für den Typ registriert oder eine Dienstaktivierung in der Komponente genehmigt.

Behandeln Sie erforderliche Dienste getrennt von überlappenden Werten, und registrieren Sie sie separat vom überlappenden Typ.

Vermeiden Sie es, einen Komponententyp mitAddCascadingValue als überlappenden Wert zu registrieren. Umschließen Sie <Router>...</Router> in der Routes-Komponente (Components/Routes.razor) stattdessen mit der Komponente, und übernehmen Sie globales interaktives serverseitiges Rendering (interaktives SSR). Ein Beispiel hierfür finden Sie im Abschnitt CascadingValue-Komponente.

CascadingValue-Komponente

Eine Vorgängerkomponente stellt einen kaskadierenden Wert mithilfe der CascadingValue-Komponente des Blazor-Frameworks bereit, die eine Unterstruktur einer Komponentenhierarchie umschließt und einen einzelnen Wert für alle Komponenten in deren Unterstruktur bereitstellt.

Das folgende Beispiel veranschaulicht den Fluss der Designinformationen nach unten in der Komponentenhierarchie, um eine CSS-Formatklasse für Schaltflächen in untergeordneten Komponenten bereitzustellen.

Die folgende ThemeInfo C#-Klasse gibt die Designinformationen an.

Hinweis

Für die Beispiele in diesem Abschnitt lautet der Namespace der App BlazorSample. Wenn Sie mit dem Code in Ihrer eigenen Beispiel-App experimentieren, ändern Sie den Namespace der App in den Namespace Ihrer eigenen Beispiel-App.

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

Die folgende Layoutkomponente gibt die Designinformation (ThemeInfo) als kaskadierenden Wert für alle Komponenten an, aus denen der Layouttext der Body-Eigenschaft besteht. ButtonClass wird ein Wert von btn-success zugewiesen, bei dem es sich um ein Bootstrapschaltflächenformat handelt. Jede Nachfolgerkomponente in der Komponentenhierarchie kann die ButtonClass-Eigenschaft über den kaskadierenden ThemeInfo-Wert verwenden.

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-Anwendungen bieten alternative Ansätze für die Kaskadierung von Werten, die breiter für die App gelten als die Bereitstellung über eine einzelne Layoutdatei:

  • Umschließen Sie das Markup der Routes-Komponente in einer CascadingValue-Komponente, um die Daten als kaskadierenden Wert für alle Komponenten der App anzugeben.

    Im folgenden Beispiel werden ThemeInfo-Daten aus der Komponente Routes kaskadiert.

    Routes.razor:

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

    Hinweis

    Das Umschließen der Routes Komponenteninstanz in der App-Komponente (Components/App.razor) mit einer CascadingValue-Komponente wird nicht unterstützt.

  • Geben Sie einen Cascading-Wert auf Stammebene als Dienst an, indem Sie die AddCascadingValue Erweiterungsmethode für den Dienstsammelgenerator aufrufen.

    Im folgenden Beispiel werden ThemeInfo-Daten aus der Datei Program kaskadiert.

    Program.cs

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

Weitere Informationen finden Sie in den folgenden Abschnitten dieses Artikels:

[CascadingParameter]-Attribut

Nachfolgerkomponenten deklarieren kaskadierende Parameter mithilfe des [CascadingParameter]-Attributs, um die kaskadierenden Werte zu nutzen. Kaskadierende Werte sind nach Typ an kaskadierende Parameter gebunden. Das Kaskadieren mehrerer Werte desselben Typs wird später in diesem Artikel im Abschnitt Kaskadieren mehrerer Werte behandelt.

Die folgende Komponente bindet den kaskadierenden ThemeInfo-Wert an einen kaskadierenden Parameter, wobei optional der gleiche Name ThemeInfo verwendet wird. Der Parameter wird verwendet, um die CSS-Klasse für die Schaltfläche Increment Counter (Themed) festzulegen.

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

Ähnlich wie bei einem regulären Komponentenparameter werden Komponenten, die einen kaskadierenden Parameter akzeptieren, noch einmal gerendert, wenn der kaskadierende Wert geändert wird. So führt z. B. das Konfigurieren einer anderen Designinstanz dazu, dass die ThemedCounter-Komponente aus dem Abschnitt der CascadingValue-Komponente gerendert wird.

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 kann verwendet werden, um anzugeben, dass sich ein kaskadierender Parameter nach der Initialisierung nicht ändert.

Kaskadierende Werte/Parameter und Rendermodusgrenzen

Kaskadierende Parameter übergeben keine Werte über Rendermodusgrenzen hinweg:

  • Interaktive Sitzungen werden in einem anderen Kontext als die Seiten ausgeführt, die statisches serverseitiges Rendering (statisches SSR) verwenden. Es ist nicht einmal erforderlich, dass der Server, der die Seite generiert, derselbe Computer ist, auf dem später eine interaktive Serversitzung gehostet wird. Dies gilt auch für WebAssembly-Komponenten, bei denen der Server ein anderer Computer als der Client ist. Der Vorteil des statischen serverseitigen Renderings (statisches SSR) besteht darin, die volle Leistung des rein zustandslosen HTML-Renderings zu erzielen.

  • Zustände, die die Grenze zwischen statischem und interaktivem Rendering überschreiten, müssen serialisierbar sein. Komponenten sind beliebige Objekte, die auf eine umfangreiche Kette anderer Objekte verweisen, einschließlich des Renderers, des DI-Containers und jeder DI-Dienstinstanz. Sie müssen explizit bewirken, dass der Zustand von statischem SSR serialisiert wird, um ihn in nachfolgenden interaktiv gerenderten Komponenten verfügbar zu machen. Es stehen zwei Ansätze zur Verfügung:

    • Über das Blazor-Framework werden Parameter, die über ein statisches SSR an interaktive Grenzen des Renderings übergeben werden, automatisch serialisiert, wenn sie JSON-serialisierbar sind, ansonsten wird ein Fehler ausgelöst.
    • Der in PersistentComponentState gespeicherte Zustand wird serialisiert und automatisch wiederhergestellt, wenn er JSON-serialisierbar ist, oder es wird ein Fehler ausgelöst.

Kaskadierende Parameter sind nicht JSON-serialisiert, da das gewöhnliche Verwendungsmuster für kaskadierende Parameter ähnlich dem bei DI-Diensten ist. Es gibt häufig plattformspezifische Varianten von kaskadierenden Parametern, daher wäre es für Entwickler*innen nicht hilfreich, wenn das Framework spezifische interaktive Serverversionen oder WebAssembly-spezifische Versionen verhindern würde. Darüber hinaus sind viele kaskadierende Parameterwerte im Allgemeinen nicht serialisierbar, daher wäre es unpraktisch, vorhandene Apps zu aktualisieren, wenn Sie dafür Verwendung aller nicht serialisierbaren kaskadierenden Parameterwerte beenden müssen.

Empfehlungen:

  • Wenn Sie den Zustand für alle interaktiven Komponenten als kaskadierenden Parameter zur Verfügung stellen müssen, wird die Verwendung von kaskadierenden Werten auf Stammebene empfohlen. Es ist ein Factorymuster verfügbar, und die App kann aktualisierte Werte nach dem Start der App ausgeben. Kaskadierende Werte auf Stammebene sind für alle Komponenten verfügbar, einschließlich interaktiver Komponenten, da sie als DI-Dienste verarbeitet werden.

  • Für Ersteller*innen von Komponentenbibliotheken können Sie wie folgt eine Erweiterungsmethode für Bibliotheksconsumer erstellen:

    builder.Services.AddLibraryCascadingParameters();
    

    Informieren Sie die Entwickler*innen, dass sie Ihre Erweiterungsmethode aufrufen sollen. Dies ist eine sinnvolle Alternative zur Anweisung, eine <RootComponent>-Komponente in ihrer MainLayout-Komponente hinzuzufügen.

Kaskadieren mehrerer Werte

Sie können mehrere Werte desselben Typs innerhalb derselben Unterstruktur kaskadieren, indem Sie jeder CascadingValue-Komponente und den entsprechenden [CascadingParameter]-Attributen eine eindeutige Name-Zeichenfolge bereitstellen.

Im folgenden Beispiel kaskadieren zwei CascadingValue-Komponenten verschiedene Instanzen von CascadingType nach Namen:

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

@code {
    private CascadingType? parentCascadeParameter1;

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

In einer Nachfolgerkomponente erhalten die kaskadierenden Parameter ihre kaskadierenden Werte von der Vorgängerkomponente nach Name:

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

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

Übergeben von Daten über eine Komponentenhierarchie

Kaskadierende Parameter ermöglichen Komponenten auch, Daten über eine Komponentenhierarchie zu übergeben. Sehen Sie sich das folgende Beispiel auf der Benutzeroberflächen-Registerkarte aus, auf der eine auf der Registerkarte festgelegte Komponente eine Reihe einzelner Tabs beibehält.

Hinweis

Für die Beispiele in diesem Abschnitt lautet der Namespace der App BlazorSample. Wenn Sie mit dem Code in Ihrer eigenen Beispiel-App experimentieren, ändern Sie den Namespace in den Namespace Ihrer eigenen Beispiel-App.

Erstellen Sie eine ITab-Schnittstelle, die von Registerkarten in einem Ordner namens UIInterfaces implementiert wird.

UIInterfaces/ITab.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample.UIInterfaces;

public interface ITab
{
    RenderFragment ChildContent { get; }
}

Hinweis

Weitere Informationen zu RenderFragment finden Sie unter ASP.NET Core Razor Komponenten.

Die folgende TabSet-Komponente verwaltet eine Reihe von Registerkarten. Die Tab-Komponenten der Registerkarten, die später in diesem Abschnitt erstellt werden, stellen die Listenelemente (<li>...</li>) für die Liste (<ul>...</ul>) bereit.

Die untergeordneten Tab-Komponenten werden nicht explizit als Parameter an TabSetübermittelt. Stattdessen sind die untergeordneten Tab-Komponenten Teil des untergeordneten Inhalts von TabSet. Allerdings muss TabSet weiterhin über jede Tab-Komponente informiert werden, damit die Header und die aktive Registerkarte gerendert werden können. Damit diese Koordination ohne zusätzlichen Code möglich ist, kann die TabSet-Komponente sich selbst als kaskadierenden Wert angeben, der dann von der Tab-Nachfolgerkomponente abgerufen wird.

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

Die Tab-Nachfolgerkomponenten erfassen das weiterführenden TabSet als kaskadierenden Parameter. Die Tab-Komponenten fügen sich selbst zu TabSet hinzu, und stimmen sich miteinander ab, um die aktive Registerkarte festzulegen.

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

Die folgende ExampleTabSet-Komponente verwendet die TabSet-Komponente, die drei Tab-Komponenten enthält.

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

Zusätzliche Ressourcen