Razor-Komponentenrendering in ASP.NET Core

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der Supportrichtlinie für .NET und .NET Core. 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.

Die aktuelle Version finden Sie in der .NET 9-Version dieses Artikels.

In diesem Artikel wird das Rendering von Razor-Komponenten in ASP.NET Core Blazor-Apps erläutert, z. B. wann StateHasChanged aufgerufen werden muss, um das Rendern einer Komponente manuell auszulösen.

Renderingkonventionen für ComponentBase

Komponenten müssen gerendert werden, wenn sie der Komponentenhierarchie von einer übergeordneten Komponente erstmalig hinzugefügt werden. Dies ist der einzige Zeitpunkt, zu dem eine Komponente gerendert werden muss. Komponenten können zu anderen Zeitpunkten gerendert werden. Dies erfolgt gemäß ihrer eigenen Logik und den entsprechenden Konventionen.

Razor-Komponenten erben von der ComponentBase-Basisklasse, die eine Logik zum Auslösen des erneuten Renderns zu den folgenden Zeitpunkten enthält:

Von ComponentBase geerbte Komponenten überspringen wiederholte Rendervorgänge aufgrund von Parameteraktualisierungen, wenn eine der folgenden Aussagen zutrifft:

  • Alle Parameter stammen aus einer Reihe bekannter Typen† oder einem beliebigen primitiven Typ, der sich seit der Festlegung der vorherigen Gruppe aus Parametern nicht geändert hat.

    †Das Framework von Blazor verwendet integrierte Regeln und Parametertypen für die Änderungserkennung. Diese Regeln und die Typen können jederzeit geändert werden. Weitere Informationen finden Sie unter ChangeDetection-API in der ASP.NET Core Referenzquelle.

    Hinweis

    Dokumentationslinks zur .NET-Referenzquelle laden in der Regel den Standardbranch des Repositorys, der die aktuelle Entwicklung für das nächste Release von .NET darstellt. Um ein Tag für ein bestimmtes Release auszuwählen, wählen Sie diesen mit der Dropdownliste Switch branches or tags (Branches oder Tags wechseln) aus. Weitere Informationen finden Sie unter How to select a version tag of ASP.NET Core source code (dotnet/AspNetCore.Docs #26205) (Auswählen eines Versionstags von ASP.NET Core-Quellcode (dotnet/AspNetCore.Docs #26205)).

  • Die Überschreibung der ShouldRenderMethode der Komponente gibt false zurück (die Standardimplementierung von ComponentBase gibt immer true zurück).

Steuern des Renderingflows

In den meisten Fällen führen ComponentBase-Konventionen zu erneuten Renderingvorgängen für die richtige Teilmenge der Komponenten, nachdem ein Ereignis auftritt. Entwickler müssen in der Regel keine manuelle Logik bereitstellen, damit das Framework die Information erhält, welche Komponenten erneut gerendert werden müssen und wann sie erneut gerendert werden müssen. Der Gesamteffekt der Konventionen des Frameworks besteht darin, dass die Komponente, für die ein Ereignis auftritt, sich selbst erneut rendert. Dies löst rekursiv ein erneutes Rendern der nachfolgenden Komponenten aus, deren Parameterwerte sich möglicherweise geändert haben.

Weitere Informationen zu den Auswirkungen auf die Leistung der Konventionen des Frameworks und dazu, wie Sie die Komponentenhierarchie einer App für das Rendern optimieren können, finden Sie unter Bewährte Methoden für die Leistung von Blazor in ASP.NET Core.

Streamingrendering

Verwenden Sie Streamingrendering mit statischem serverseitigen Rendering (SSR) oder Prerendering, um Inhaltsupdates für den Antwortdatenstrom zu streamen und die Benutzererfahrung für Komponenten zu verbessern, die langwierige asynchrone Aufgaben zum vollständigen Rendern ausführen.

Betrachten Sie beispielsweise eine Komponente, die beim Laden der Seite eine langwierige Datenbankabfrage oder einen Web-API-Aufruf zum Rendern von Daten durchführt. Normalerweise müssen asynchrone Aufgaben, die im Rahmen des Renderns einer serverseitigen Komponente ausgeführt werden, abgeschlossen werden, bevor die gerenderte Antwort gesendet wird, wodurch sich das Laden der Seite verzögern kann. Jede erhebliche Verzögerung beim Rendern der Seite beeinträchtigt die Benutzererfahrung. Um die Benutzererfahrung zu verbessern, wird beim Streamingrendering zunächst die gesamte Seite schnell mit Platzhalterinhalten gerendert, während asynchrone Vorgänge ausgeführt werden. Nach Abschluss der Vorgänge wird der aktualisierte Inhalt über dieselbe Antwortverbindung an den Client gesendet und in das DOM eingefügt.

Für das Streamingrendering darf der Server die Ausgabe nicht puffern. Die Antwortdaten müssen beim Generieren der Daten an den Client fließen. Für Hosts, die eine Pufferung erzwingen, wird das Streamingrendering langsam beeinträchtigt, und die Seite wird ohne Streamingrendering geladen.

Um Inhaltsaktualisierungen bei Verwendung des statischen serverseitigen Renderings (statisches SSR) oder Prerenderings zu streamen, wenden Sie das [StreamRendering(true)]-Attribut auf die Komponente an. Streamingrendering muss explizit aktiviert werden, da gestreamte Updates dazu führen können, dass Inhalte auf der Seite verschoben werden. Komponenten ohne das Attribut übernehmen automatisch Streamingrendering, wenn die übergeordnete Komponente das Feature verwendet. Übergeben Sie false an das Attribut in einer untergeordneten Komponente, um das Feature an dieser Stelle und weiter unten im Teilbaum der Komponente zu deaktivieren. Das Attribut ist funktionsfähig, wenn es auf Komponenten, die von einer Razor Klassenbibliothek bereitgestellt werden, angewendet wird.

Das folgende Beispiel basiert auf der Weather-Komponente in einer App, die mit der Blazor Web App-Projektvorlage erstellt wurde. Der Aufruf von Task.Delay simuliert das asynchrone Abrufen von Wetterdaten. Die Komponente rendert zunächst Platzhalterinhalte („Loading...“), ohne auf den Abschluss der asynchronen Verzögerung zu warten. Wenn die asynchrone Verzögerung abgeschlossen und der Inhalt der Wetterdaten generiert ist, wird der Inhalt zur Antwort gestreamt und in die Wettervorhersagetabelle eingefügt.

Weather.razor:

@page "/weather"
@attribute [StreamRendering(true)]

...

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        ...
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    ...

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(500);

        ...

        forecasts = ...
    }
}

Unterdrücken der UI-Aktualisierung (ShouldRender)

ShouldRender wird jedes Mal aufgerufen, wenn eine Komponente gerendert wird. Setzen Sie ShouldRender außer Kraft, um die Aktualisierung der Benutzeroberfläche zu verwalten. Wenn die Implementierung true zurückgibt, wird die Benutzeroberfläche aktualisiert.

Selbst wenn ShouldRender außer Kraft gesetzt wird, wird die Komponente immer anfänglich gerendert.

ControlRender.razor:

@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<PageTitle>Control Render</PageTitle>

<h1>Control Render Example</h1>

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender() => shouldRender;

    private void IncrementCount() => currentCount++;
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

    private void IncrementCount()
    {
        currentCount++;
    }
}
@page "/control-render"

<label>
    <input type="checkbox" @bind="shouldRender" />
    Should Render?
</label>

<p>Current count: @currentCount</p>

<p>
    <button @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;
    private bool shouldRender = true;

    protected override bool ShouldRender()
    {
        return shouldRender;
    }

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

Weitere Informationen zu bewährten Methoden für die Leistung in Bezug auf ShouldRenderfinden Sie unter Bewährte Methoden für die Leistung von Blazor in ASP.NET Core.

StateHasChanged

Das Aufrufen von StateHasChanged-Warteschlangen ist ein erneutes Rendern, das ausgeführt wird, wenn der Hauptthread der App frei ist.

Komponenten werden für die Darstellung in eine Warteschlange gestellt und nicht erneut in eine Warteschlange gestellt, wenn bereits ein erneutes Rendering aussteht. Wenn eine Komponente StateHasChanged fünfmal hintereinander in einer Schleife aufruft, wird die Komponente nur einmal gerendert. Dieses Verhalten ist in ComponentBase kodiert, das zuerst prüft, ob es über ein Rerendering verfügt, bevor es ein weiteres in die Warteschlange stellt.

Eine Komponente kann während desselben Zyklus mehrmals gerendert werden, was häufig auftritt, wenn eine Komponente über untergeordnete Elemente verfügt, die miteinander interagieren:

  • Eine übergeordnete Komponente rendert mehrere untergeordnete Elemente.
  • Untergeordnete Komponenten rendern und lösen eine Aktualisierung des übergeordneten Elements aus.
  • Eine übergeordnete Komponente wird mit neuem Status neu gerendert.

Dieses Design ermöglicht es, StateHasChanged bei Bedarf aufzurufen, ohne das Risiko einzugehen, unnötige Renderings zu erstellen. Sie können dieses Verhalten in einzelnen Komponenten jederzeit kontrollieren, indem Sie IComponent direkt implementieren und die Komponente beim Rendern manuell bearbeiten.

Betrachten Sie die folgende IncrementCount-Methode, die eine Zählung erhöht, StateHasChanged aufruft und die Zählung erneut erhöht:

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

Wenn Sie den Code im Debugger durchgehen, könnte man meinen, dass die Zählung in der Benutzeroberfläche für die erste currentCount++-Ausführung unmittelbar nach dem Aufruf von StateHasChanged aktualisiert wird. Die Benutzeroberfläche zeigt jedoch an diesem Punkt aufgrund der synchronen Verarbeitung für die Ausführung dieser Methode keine aktualisierte Anzahl an. Der Renderer hat erst dann die Möglichkeit, das Element zu rendern, wenn der Eventhandler abgeschlossen ist. Die UI-Anzeigen erhöhen sich für beide currentCount++-Ausführungen in einem einzigen Rendering.

Wenn Sie zwischen den currentCount++-Zeilen eine „await“-Funktion integriert haben, bietet der „await“-Aufruf dem Renderer die Möglichkeit, zu rendern. Dies hat dazu geführt, dass einige Entwickelnde Delay mit einer Verzögerung von einer Millisekunde in ihren Komponenten aufrufen, um ein Rendering zu ermöglichen. Wir empfehlen jedoch nicht, eine App willkürlich zu verlangsamen, um ein Rendering in die Warteschlange zu stellen.

Der beste Ansatz ist, auf Task.Yield zu warten (mit der „await“-Funktion), wodurch die Komponente gezwungen wird, den Code asynchron zu verarbeiten und im aktuellen Batch ein zweites Rendering in einem separaten Batch durchzuführen, sobald die zurückgegebene Aufgabe fortgesetzt wird.

Betrachten Sie die folgende überarbeitete IncrementCount-Methode, die die Benutzeroberfläche zweimal aktualisiert, da das von StateHasChanged in die Warteschlange gestellte Rendering ausgeführt wird, wenn die Aufgabe mit dem Aufruf von Task.Yield übergeben wird:

private async Task IncrementCount()
{
    currentCount++;
    StateHasChanged();
    await Task.Yield();
    currentCount++;
}

Achten Sie darauf, nicht unnötig StateHasChanged anzurufen, denn das ist ein häufiger Fehler, der unnötige Rendingkosten verursacht. Code sollte unter den folgenden Umständen StateHasChanged nicht aufrufen müssen:

  • Routinemäßige Verarbeitung von Ereignissen (synchron und asynchron), da ComponentBase ein Rendering für die meisten Routingereignishandler auslöst
  • Implementieren typischer Lebenszykluslogik (synchron und asynchron), z. B. OnInitialized oder OnParametersSetAsync, da ComponentBase ein Rendering für typische Lebenszyklusereignisse auslöst

In den Fällen, die in den folgenden Abschnitten dieses Artikels beschrieben werden, kann es jedoch sinnvoll sein, StateHasChanged aufzurufen:

Ein asynchroner Handler beinhaltet mehrere asynchrone Phasen

Aufgrund der Art, wie Aufgaben in .NET definiert werden, kann ein Empfänger von Task nur den endgültigen Abschluss beobachten, keine asynchronen Zwischenzustände. Deshalb kann ComponentBase nur ein erneutes Rendering auslösen, wenn Task zuerst zurückgegeben wird und wenn Task endgültig abgeschlossen ist. Das Framework weiß nicht, dass eine Komponente an anderen Zwischenpunkten erneut gerendert werden soll, zum Beispiel, wenn eine IAsyncEnumerable<T>-Schnittstelle Daten als eine Reihe von ZwischenpunkteTaskn zurückgibt. Wenn Sie an Zwischenpunkten ein erneutes Rendering durchführen möchten, rufen Sie StateHasChanged an diesen Punkten auf.

Betrachten Sie die folgende CounterState1-Komponente, die bei jeder Ausführung der IncrementCount-Methode den Zählerstand viermal aktualisiert:

  • Automatische Rendervorgänge erfolgen nach dem ersten und letzten Inkrement von currentCount.
  • Manuelle Rendervorgänge werden durch Aufrufe von StateHasChanged ausgelöst, wenn das Framework nicht automatisch Rendervorgänge bei Zwischenverarbeitungspunkten auslöst, bei denen currentCount inkrementiert wird.

CounterState1.razor:

@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<PageTitle>Counter State 1</PageTitle>

<h1>Counter State Example 1</h1>

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}
@page "/counter-state-1"

<p>
    Current count: @currentCount
</p>

<p>
    <button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
</p>

@code {
    private int currentCount = 0;

    private async Task IncrementCount()
    {
        currentCount++;
        // Renders here automatically

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        StateHasChanged();

        await Task.Delay(1000);
        currentCount++;
        // Renders here automatically
    }
}

Ein Aufruf von einer externen Quelle an das Blazor-Renderingsystem bzw. -Ereignisverarbeitungssystem wird empfangen

ComponentBase kennt nur die eigenen Lebenszyklusmethoden und von Blazor ausgelösten Ereignisse. ComponentBase hat keine Informationen zu anderen Ereignissen, die möglicherweise im Code auftreten. Alle C#-Ereignisse, die von einem benutzerdefinierten Datenspeicher ausgelöst werden, sind beispielsweise für Blazor nicht bekannt. Damit solche Ereignisse ein erneutes Rendering auslösen, damit aktualisierte Werte auf der Benutzeroberfläche angezeigt werden, rufen Sie StateHasChanged auf.

Berücksichtigen Sie die folgende CounterState2-Komponente, die System.Timers.Timer verwendet, um eine Anzahl in regelmäßigen Abständen zu aktualisieren, und StateHasChanged aufruft, um die Benutzeroberfläche zu aktualisieren:

  • OnTimerCallback wird außerhalb eines von Blazor verwalteten Renderingflows oder einer Ereignisbenachrichtigung ausgeführt. OnTimerCallback muss daher StateHasChanged aufrufen, weil Blazor die Änderungen an currentCount im Rückruf nicht bekannt sind.
  • Die Komponente implementiert IDisposable, wobei Timer verworfen wird, wenn das Framework die Dispose-Methode aufruft. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Da der Rückruf außerhalb des Synchronisierungskontexts von Blazor aufgerufen wird, muss die Komponente die Logik von OnTimerCallback in ComponentBase.InvokeAsync verpacken, um sie in den Synchronisierungskontext des Renderers zu verschieben. Dies ist äquivalent zum Marshallen des Benutzeroberflächenthreads in anderen Benutzeroberflächenframeworks. StateHasChanged kann nur im Synchronisierungskontext des Renderers aufgerufen werden und löst andernfalls eine Ausnahme aus:

System.InvalidOperationException: „Der aktuelle Thread ist nicht dem Dispatcher zugeordnet. Verwenden Sie InvokeAsync() zum Wechseln der Ausführung auf den Dispatcher beim Auslösen des Renderings oder des Komponentenzustands.“

CounterState2.razor:

@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<PageTitle>Counter State 2</PageTitle>

<h1>Counter State Example 2</h1>

<p>
    This counter demonstrates <code>Timer</code> disposal.
</p>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}
@page "/counter-state-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>
    Current count: @currentCount
</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Eine Komponente soll außerhalb der untergeordneten Struktur gerendert werden, die von einem bestimmten Ereignis erneut gerendert wird

Die Benutzeroberfläche kann Folgendes umfassen:

  1. Senden eines Ereignisses an eine Komponente.
  2. Ändern eines Zustands.
  3. Rendern einer völlig anderen Komponente, die kein Nachkomme der Komponente ist, die das Ereignis empfängt.

Eine Möglichkeit, mit diesem Szenario umzugehen, ist die Bereitstellung einer Zustandsverwaltungsklasse, häufig als DI-Dienst (Dependency Injection, Abhängigkeitsinjektion), der in mehrere Komponenten injiziert wird. Wenn eine Komponente eine Methode für den Status-Manager aufruft, löst der Status-Manager ein C#-Ereignis aus, das dann von einer unabhängigen Komponente empfangen wird.

Ansätze zum Verwalten des Zustands finden Sie in den folgenden Ressourcen:

Für den Zustands-Manager-Ansatz befinden sich C#-Ereignisse außerhalb der Blazor-Renderingpipeline. Rufen Sie StateHasChanged für andere Komponenten auf, die Sie als Reaktion auf die Ereignisse des Zustands-Managers erneut rendern möchten.

Der Zustands-Manager-Ansatz ähnelt dem Fall mit System.Timers.Timer im vorherigen Abschnitt. Da die Ausführungsaufrufliste in der Regel im Synchronisierungskontext des Renderers verbleibt, ist der Aufruf von InvokeAsync normalerweise nicht erforderlich. Der Aufruf von InvokeAsync ist nur erforderlich, wenn sich die Logik außerhalb des Synchronisierungskontexts befindet, z. B. wenn ContinueWith für Task aufgerufen wird oder Task mit ConfigureAwait(false) erwartet wird. Weitere Informationen finden Sie im Abschnitt Ein Aufruf von einer externen Quelle an das Blazor-Renderingsystem bzw. -Ereignisverarbeitungssystem wird empfangen.

WebAssembly-Ladestatusanzeige für Blazor Web Apps

Eine Ladestatusanzeige ist in einer App, die aus der Blazor Web App-Projektvorlage erstellt wurde, nicht vorhanden. Für eine zukünftige Version von .NET ist ein neues Feature zur Ladestatusanzeige geplant. In der Zwischenzeit kann eine App benutzerdefinierten Code übernehmen, um eine Ladestatusanzeige zu erstellen. Weitere Informationen finden Sie unter Starten von ASP.NET CoreBlazor.