Freigeben über


ASP.NET Core Razor-Komponentenlebenszyklus

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Informationen zum aktuellen Release finden Sie in der .NET 8-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.

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

In diesem Artikel werden der ASP.NET Core Razor-Komponentenlebenszyklus und die Verwendung von Lebenszyklusereignissen erläutert.

Lebenszyklusereignisse

Die Razor-Komponente verarbeitet Razor-Ereignisse des Komponentenlebenszyklus in einer Reihe von synchronen und asynchronen Lebenszyklusmethoden. Die Lebenszyklusmethoden können außer Kraft gesetzt werden, um während der Komponenteninitialisierung und des Renderings zusätzliche Vorgänge mit Komponenten durchzuführen.

Dieser Artikel vereinfacht die Verarbeitung von Komponentenlebenszyklusereignissen, um die komplexe Frameworklogik zu verdeutlichen, und deckt nicht alle Änderungen ab, die über die Jahre vorgenommen wurden. Möglicherweise müssen Sie auf die ComponentBase-Verweisquelle zugreifen, um die benutzerdefinierte Ereignisverarbeitung in die Lebenszyklusereignisverarbeitung von Blazor zu integrieren. Codekommentare in der Verweisquelle enthalten zusätzliche Hinweise zur Lebenszyklusereignisverarbeitung, die in diesem Artikel oder in der API-Dokumentation nicht enthalten sind.

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 folgenden vereinfachten Diagramme veranschaulichen die Verarbeitung von Lebenszyklusereignissen der Razor-Komponente. Die C#-Methoden, die den Lebenszyklusereignissen zugeordnet sind, werden anhand von Beispielen in den folgenden Abschnitten dieses Artikels definiert.

Ereignisse des Komponentenlebenszyklus:

  1. Wenn die Komponente erstmalig ein Rendering für eine Anforderung ausführt, führen Sie folgende Schritte durch:
    • Erstellen Sie die Instanz der Komponente.
    • Führen Sie eine Eigenschaftsinjektion durch.
    • Rufen Sie OnInitialized{Async} auf. Wenn ein unvollständiges Task-Element zurückgegeben wird, wird auf Task gewartet. Anschließend wird die Komponente erneut gerendert. Die synchrone Methode wird vor der asychronen Methode aufgerufen.
  2. Rufen Sie OnParametersSet{Async} auf. Wenn ein unvollständiges Task-Element zurückgegeben wird, wird auf Task gewartet. Anschließend wird die Komponente erneut gerendert. Die synchrone Methode wird vor der asychronen Methode aufgerufen.
  3. Rendering für alle synchronen Arbeiten und vollständigen Task-Elemente.

Hinweis

Asynchrone Aktionen, die in Lebenszyklusereignissen ausgeführt werden, werden möglicherweise nicht abgeschlossen, bevor eine Komponente gerendert wird. Weitere Informationen finden Sie im Abschnitt „Behandeln unvollständiger asynchroner Aktionen beim Rendern“ weiter unten in diesem Artikel.

Eine übergeordnete Komponente wird vor den untergeordneten Komponenten gerendert, da das Rendern bestimmt, welche untergeordneten Elemente vorhanden sind. Wenn die synchrone Initialisierung der übergeordneten Komponente verwendet wird, wird die übergeordnete Initialisierung garantiert zuerst abgeschlossen. Wenn die asynchrone Initialisierung der übergeordneten Komponente verwendet wird, kann die Abschlussreihenfolge der Initialisierung der übergeordneten und untergeordneten Komponente nicht bestimmt werden, da sie vom ausgeführten Initialisierungscode abhängt.

Komponentenlebenszyklusereignisse einer Razor-Komponente in Blazor

DOM-Ereignisverarbeitung:

  1. Der Ereignishandler wird ausgeführt.
  2. Wenn ein unvollständiges Task-Element zurückgegeben wird, wird auf Task gewartet. Anschließend wird die Komponente erneut gerendert.
  3. Rendering für alle synchronen Arbeiten und vollständigen Task-Elemente.

DOM-Ereignisverarbeitung

Der Render-Lebenszyklus:

  1. Vermeiden Sie weitere Renderingvorgänge für die Komponente, wenn beide der folgenden Bedingungen erfüllt sind:
    • Es ist nicht das erste Rendern.
    • false gibt ShouldRender zurück.
  2. Erstellen Sie das Diff (Unterschied) der Renderstruktur, und rendern Sie die Komponente.
  3. Warten Sie, bis das DOM aktualisiert wurde.
  4. Rufen Sie OnAfterRender{Async} auf. Die synchrone Methode wird vor der asychronen Methode aufgerufen.

Lebenszyklus von Rendervorgängen

Wenn StateHasChanged von Entwicklern aufgerufen wird, führt dies zu einem Rendervorgang. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Wenn Parameter festgelegt wurden (SetParametersAsync)

SetParametersAsync legt Parameter fest, die vom übergeordneten Element der Komponente in der Renderstruktur oder aus Routingparametern bereitgestellt werden.

Der Parameter ParameterView der Methode enthält bei jedem Aufruf von SetParametersAsync den Satz von Komponentenparameterwerten für die Komponente. Durch Überschreiben dieser SetParametersAsync-Methode kann Entwicklercode direkt mit den Parametern von ParameterView interagieren.

Die Standardimplementierung von SetParametersAsync legt den Wert der einzelnen Eigenschaften mit dem [Parameter]- oder [CascadingParameter]-Attribut fest, die einen entsprechenden Wert in ParameterView aufweisen. Parameter, die keinen entsprechenden Wert in ParameterView haben, bleiben unverändert.

Im Allgemeinen sollte ihr Code die Basisklassenmethode (await base.SetParametersAsync(parameters);) aufrufen, wenn SetParametersAsync überschrieben wird. In erweiterten Szenarien kann Entwicklercode die Werte der eingehenden Parameter auf beliebige Weise interpretieren, die das Aufrufen der Basisklassenmethode nicht erfordert. Beispielsweise ist es nicht erforderlich, die eingehenden Parameter den Eigenschaften der Klasse zuzuordnen. Sie müssen jedoch auf die ComponentBase-Referenzquelle verweisen, wenn Sie den Code strukturieren, ohne die Basisklassenmethode aufzurufen, da sie andere Lebenszyklusmethoden aufruft und das Rendern in komplexer Weise auslöst.

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)).

Wenn Sie die Initialisierungs- und Rendering-API von ComponentBase.SetParametersAsync nutzen, aber keine eingehenden Parameter verarbeiten möchten, haben Sie die Möglichkeit, eine leere ParameterView an die Basisklassenmethode zu übergeben:

await base.SetParametersAsync(ParameterView.Empty);

Wenn Ereignishandler im Entwicklercode bereitgestellt werden, wenden Sie Unhook bei der Bereinigung an. Weitere Informationen finden Sie im Abschnitt Beseitigung von Komponenten mit IDisposable und IAsyncDisposable.

Im folgenden Beispiel weist ParameterView.TryGetValue den Wert des Param-Parameters value zu, wenn die Analyse eines Routingparameters für Param erfolgreich ist. Wenn value nicht null lautet, wird der Wert von der Komponente angezeigt.

Beim Abgleich von Routenparametern wird die Groß-/Kleinschreibung zwar nicht beachtet, aber in der Routenvorlage stimmt nur TryGetValue mit Parameternamen überein, bei denen die Groß-/Kleinschreibung beachtet wird. Das folgende Beispiel erfordert die Verwendung von /{Param?} in der Routenvorlage, um den Wert mit TryGetValue und nicht mit /{param?} abzurufen. Wenn /{param?} in diesem Szenario verwendet wird, gibt TryGetValuefalse zurück, und message wird auf keine der message-Zeichenfolgen festgelegt.

SetParamsAsync.razor:

@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<PageTitle>Set Parameters Async</PageTitle>

<h1>Set Parameters Async Example</h1>

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string? Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async/{Param?}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}
@page "/set-params-async"
@page "/set-params-async/{Param}"

<p>@message</p>

@code {
    private string message = "Not set";

    [Parameter]
    public string Param { get; set; }

    public override async Task SetParametersAsync(ParameterView parameters)
    {
        if (parameters.TryGetValue<string>(nameof(Param), out var value))
        {
            if (value is null)
            {
                message = "The value of 'Param' is null.";
            }
            else
            {
                message = $"The value of 'Param' is {value}.";
            }
        }

        await base.SetParametersAsync(parameters);
    }
}

Komponenteninitialisierung (OnInitialized{Async})

OnInitialized und OnInitializedAsync werden ausschließlich verwendet, um eine Komponente für die gesamte Lebensdauer der Komponenteninstanz zu initialisieren. Parameterwerte und Parameterwertänderungen sollten sich nicht auf die Initialisierung auswirken, die in diesen Methoden ausgeführt wird. Beispielsweise wird das Laden statischer Optionen in eine Dropdownliste, die sich während der Lebensdauer der Komponente nicht ändert und nicht von Parameterwerten abhängig ist, in einer dieser Lebenszyklusmethoden ausgeführt. Wenn Parameterwerte oder Änderungen in Parameterwerten sich auf den Komponentenzustand auswirken, verwenden Sie stattdessen OnParametersSet{Async}.

Diese Methoden werden aufgerufen, wenn die Komponente initialisiert wird, nachdem sie ihre anfänglichen Parameter in SetParametersAsync erhalten hat. Die synchrone Methode wird vor der asychronen Methode aufgerufen.

Wenn die synchrone Initialisierung der übergeordneten Komponente verwendet wird, wird die übergeordnete Initialisierung garantiert vor der Initialisierung der untergeordneten Komponente abgeschlossen. Wenn die asynchrone Initialisierung der übergeordneten Komponente verwendet wird, kann die Abschlussreihenfolge der Initialisierung der übergeordneten und untergeordneten Komponente nicht bestimmt werden, da sie vom ausgeführten Initialisierungscode abhängt.

Für einen synchronen Betrieb setzen Sie OnInitialized außer Kraft:

OnInit.razor:

@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<PageTitle>On Initialized</PageTitle>

<h1>On Initialized Example</h1>

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized() => 
        message = $"Initialized at {DateTime.Now}";
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string? message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}
@page "/on-init"

<p>@message</p>

@code {
    private string message;

    protected override void OnInitialized()
    {
        message = $"Initialized at {DateTime.Now}";
    }
}

Überschreiben Sie OnInitializedAsync, und verwenden Sie den Operator await, um einen asynchronen Vorgang durchzuführen:

protected override async Task OnInitializedAsync()
{
    await ...
}

Wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Initialisierungslogik verwendet wird, rufen Sie OnInitializedAsync auf der Basisklasse auf:

protected override async Task OnInitializedAsync()
{
    await ...

    await base.OnInitializedAsync();
}

Es ist nicht erforderlich, ComponentBase.OnInitializedAsync aufzurufen, außer wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Logik verwendet wird. Weitere Informationen finden Sie im Abschnitt Lebenszyklusmethoden der Basisklasse.

Blazor-Apps, die ihren Inhalt auf dem Server vorab rendern, rufen OnInitializedAsync zweimal auf:

  • Einmal, wenn die Komponente anfänglich statisch als Teil der Seite gerendert wird.
  • Ein zweites Mal, wenn der Browser die Komponente rendert.

Informationen zum Verhindern, dass Entwicklercode beim Vorabrendern in OnInitializedAsync zwei Mal ausgeführt wird, finden Sie im Abschnitt Zustandsbehaftete erneute Verbindung nach Vorabrendern. Der Inhalt des Abschnitts konzentriert sich auf Blazor Web App-Web-Apps und die zustandsbehaftete SignalR-Neuverbindung. Informationen zum Beibehalten des Zustands während der Ausführung von Initialisierungscode während des Prerenderings finden Sie unter Prerendering von Razor-Komponenten in ASP.NET Core.

Informationen zum Verhindern, dass Entwicklercode beim Vorabrendern in OnInitializedAsync zwei Mal ausgeführt wird, finden Sie im Abschnitt Zustandsbehaftete erneute Verbindung nach Vorabrendern. Obwohl sich der Inhalt im Abschnitt auf Blazor Server und die zustandsbehaftete SignalR-Verbindungswiederherstellung konzentriert, umfasst das Szenario für Vorabrendern in gehosteten Blazor WebAssembly-Lösungen (WebAssemblyPrerendered) ähnliche Bedingungen und Verfahren, um die doppelte Ausführung von Entwicklercode zu verhindern. Informationen zum Beibehalten des Zustands während der Ausführung von Initialisierungscode während des Prerenderings finden Sie unter Prerendering und Integrieren von Razor-Komponenten in ASP.NET Core.

Während eine Blazor-App vorab gerendert wird, sind bestimmte Aktionen nicht möglich, z. B. Aufrufe in JavaScript (JS-interop). Komponenten müssen wahrscheinlich unterschiedlich rendern, wenn dafür ein Prerendering durchgeführt wurde. Weitere Informationen finden Sie im Abschnitt Voarabrendering mit JavaScript-Interop.

Wenn Ereignishandler im Entwicklercode bereitgestellt werden, wenden Sie Unhook bei der Bereinigung an. Weitere Informationen finden Sie im Abschnitt Beseitigung von Komponenten mit IDisposable IAsyncDisposableIDisposableIAsyncDisposable.

Verwenden Sie Streamingrendering mit statischem serverseitigem Rendering (SSR) oder Vorabrendering, um die Benutzererfahrung für Komponenten zu verbessern, die asynchrone Aufgaben mit langer Ausführungszeit in OnInitializedAsync ausführen, bis sie vollständig gerendert worden sind. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Nachdem Parameter festgelegt wurden (OnParametersSet{Async})

OnParametersSet oder OnParametersSetAsync wird aufgerufen:

  • Nachdem die Komponente in OnInitialized oder OnInitializedAsync initialisiert wurde.

  • Wenn die übergeordnete Komponente erneut gerendert wird und Folgendes bereitstellt:

    • Bekannte oder einfache unveränderliche Typen, wenn sich mindestens ein Parameter geändert hat.
    • Parameter mit komplexem Typ. Das Framework kann nicht wissen, ob die Werte eines Parameters mit komplexem Typ intern mutiert sind, daher behandelt das Framework den Parametersatz immer als geändert, wenn mindestens ein Parameter mit komplexem Typ vorhanden ist.

    Weitere Informationen über Renderingkonventionen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Die synchrone Methode wird vor der asychronen Methode aufgerufen.

Die Methoden können auch dann aufgerufen werden, wenn sich die Parameterwerte nicht geändert haben. Dieses Verhalten unterstreicht die Notwendigkeit, dass Entwickler zusätzliche Logik in den Methoden implementieren müssen, um zu überprüfen, ob sich die Parameterwerte tatsächlich geändert haben, bevor von diesen Parametern abhängige Daten oder Zustände neu initialisiert werden.

Navigieren Sie für die folgende Beispielkomponente unter einer URL zu der Seite der Komponente:

  • Mit einem Startdatum, das von StartDate empfangen wird: /on-parameters-set/2021-03-19
  • Ohne Startdatum, wobei StartDate einem Wert der aktuellen Ortszeit zugewiesen wird: /on-parameters-set

Hinweis

In einer Komponentenroute ist es nicht möglich, einen DateTime-Parameter mit der Routenbeschränkung datetime einzuschränken und den Parameter optional zu machen. Daher verwendet die folgende OnParamsSet-Komponente zwei @page-Anweisungen, um das Routing mit und ohne ein bereitgestelltes Datumssegment in der URL zu verarbeiten.

OnParamsSet.razor:

@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<PageTitle>On Parameters Set</PageTitle>

<h1>On Parameters Set Example</h1>

<p>
    Pass a datetime in the URI of the browser's address bar. 
    For example, add <code>/1-1-2024</code> to the address.
</p>

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied " +
                $"(StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used " +
                $"(StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string? message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}
@page "/on-params-set"
@page "/on-params-set/{StartDate:datetime}"

<p>@message</p>

@code {
    private string message;

    [Parameter]
    public DateTime StartDate { get; set; }

    protected override void OnParametersSet()
    {
        if (StartDate == default)
        {
            StartDate = DateTime.Now;

            message = $"No start date in URL. Default value applied (StartDate: {StartDate}).";
        }
        else
        {
            message = $"The start date in the URL was used (StartDate: {StartDate}).";
        }
    }
}

Asynchrones Arbeiten bei der Anwendung von Parametern und Eigenschaftswerten muss während des OnParametersSetAsync-Lebenszyklusereignisses erfolgen:

protected override async Task OnParametersSetAsync()
{
    await ...
}

Wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Initialisierungslogik verwendet wird, rufen Sie OnParametersSetAsync auf der Basisklasse auf:

protected override async Task OnParametersSetAsync()
{
    await ...

    await base.OnParametersSetAsync();
}

Es ist nicht erforderlich, ComponentBase.OnParametersSetAsync aufzurufen, außer wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Logik verwendet wird. Weitere Informationen finden Sie im Abschnitt Lebenszyklusmethoden der Basisklasse.

Wenn Ereignishandler im Entwicklercode bereitgestellt werden, wenden Sie Unhook bei der Bereinigung an. Weitere Informationen finden Sie im Abschnitt Beseitigung von Komponenten mit IDisposable IAsyncDisposableIDisposableIAsyncDisposable.

Weitere Informationen zu Routenparametern und Einschränkungen finden Sie unter ASP.NET Core ASP.NET Core: Routing und Navigation in Blazor.

Ein Beispiel für die manuelle Implementierung von SetParametersAsync zur Verbesserung der Leistung in einigen Szenarien finden Sie unter ASP.NET Core Blazor bewährte Methoden für Leistung.

Nach dem Rendern der Komponente (OnAfterRender{Async})

OnAfterRender und OnAfterRenderAsync werden aufgerufen, nachdem eine Komponente interaktiv gerendert und die Benutzeroberfläche aktualisiert wurde (z. B. nach dem Hinzufügen von Elementen zum Browser-DOM). Element- und Komponentenverweise werden an dieser Stelle aufgefüllt. Verwenden Sie diese Stufe, um zusätzliche Initialisierungsschritte mit dem gerenderten Inhalt durchzuführen, z. B. JS-Interop-Aufrufe, die mit den gerenderten DOM-Elementen interagieren. Die synchrone Methode wird vor der asychronen Methode aufgerufen.

Diese Methoden werden während des Vorab- oder statischen serverseitigen Renderings (SSR) auf dem Server nicht aufgerufen, weil diese Prozesse nicht an ein Livebrowser-DOM angefügt und bereits abgeschlossen sind, bevor das DOM aktualisiert wird.

Bei OnAfterRenderAsync wird die Komponente nach Abschluss aller zurückgegebenen Tasks nicht automatisch erneut gerendert, um eine endlose Renderschleife zu vermeiden.

OnAfterRender und OnAfterRenderAsync werden aufgerufen, nachdem eine Komponente das Rendering beendet hat. Element- und Komponentenverweise werden an dieser Stelle aufgefüllt. Verwenden Sie diese Stufe, um zusätzliche Initialisierungsschritte mit dem gerenderten Inhalt durchzuführen, z. B. JS-Interop-Aufrufe, die mit den gerenderten DOM-Elementen interagieren. Die synchrone Methode wird vor der asychronen Methode aufgerufen.

Diese Methoden werden während des Vorabrenderings nicht aufgerufen, da das Vorabrendering nicht an ein Livebrowser-DOM angefügt und bereits abgeschlossen ist, bevor das DOM aktualisiert wird.

Bei OnAfterRenderAsync wird die Komponente nach Abschluss aller zurückgegebenen Tasks nicht automatisch erneut gerendert, um eine endlose Renderschleife zu vermeiden.

Der Parameter firstRender für OnAfterRender und OnAfterRenderAsync:

  • Wird auf true festgelegt, wenn die Komponenteninstanz zum ersten Mal gerendert wird.
  • Kann verwendet werden, um sicherzustellen, dass die Initialisierung nur einmal durchgeführt wird.

AfterRender.razor:

@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender) =>
        Logger.LogInformation("firstRender = {FirstRender}", firstRender);

    private void HandleClick() => Logger.LogInformation("HandleClick called");
}
@page "/after-render"
@inject ILogger<AfterRender> Logger

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@inject ILogger<AfterRender> Logger 

<PageTitle>After Render</PageTitle>

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger 

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}
@page "/after-render"
@using Microsoft.Extensions.Logging
@inject ILogger<AfterRender> Logger

<h1>After Render Example</h1>

<p>
    <button @onclick="HandleClick">Log information (and trigger a render)</button>
</p>

<p>Study logged messages in the console.</p>

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        Logger.LogInformation("OnAfterRender: firstRender = {FirstRender}", firstRender);
    }

    private void HandleClick()
    {
        Logger.LogInformation("HandleClick called");
    }
}

Im Beispiel AfterRender.razor wird die folgende Ausgabe auf der Konsole erzeugt, wenn die Seite geladen und die Schaltfläche ausgewählt wird:

OnAfterRender: firstRender = True
HandleClick called
OnAfterRender: firstRender = False

Asynchrones Arbeiten unmittelbar nach dem Rendering muss während des OnAfterRenderAsync-Lebenszyklusereignisses erfolgen:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...
}

Wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Initialisierungslogik verwendet wird, rufen Sie OnAfterRenderAsync auf der Basisklasse auf:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    ...

    await base.OnAfterRenderAsync(firstRender);
}

Es ist nicht erforderlich, ComponentBase.OnAfterRenderAsync aufzurufen, außer wenn eine benutzerdefinierte Basisklasse mit benutzerdefinierter Logik verwendet wird. Weitere Informationen finden Sie im Abschnitt Lebenszyklusmethoden der Basisklasse.

Selbst wenn Sie ein Task von OnAfterRenderAsync zurückgeben, plant das Framework keinen weiteren Renderzyklus für Ihre Komponente ein, sobald diese Aufgabe abgeschlossen ist. Damit soll eine unendliche Renderschleife vermieden werden. Dies unterscheidet sich von den anderen Lebenszyklusmethoden, die einen weiteren Renderzyklus planen, sobald ein zurückgegebener Task abgeschlossen ist.

OnAfterRender und OnAfterRenderAsync werden beim Vorabrendern auf dem Server nicht aufgerufen. Die Methoden werden aufgerufen, wenn die Komponente nach dem Vorabrendern interaktiv gerendert wird. Beim Vorabrendern der App:

  1. Die Komponente wird auf dem Server ausgeführt, um in der HTTP-Antwort statisches HTML-Markup zu generieren. Während dieser Phase werden OnAfterRender und OnAfterRenderAsync nicht aufgerufen.
  2. Wenn das Blazor-Skript (blazor.{server|webassembly|web}.js) im Browser gestartet wird, wird die Komponente in einem interaktiven Renderingmodus neu gestartet. Nachdem eine Komponente neu gestartet wurde, werden OnAfterRender und OnAfterRenderAsync aufgerufen, da sich die App nicht mehr in der Phase des Vorabrenderns befindet.

Wenn Ereignishandler im Entwicklercode bereitgestellt werden, wenden Sie Unhook bei der Bereinigung an. Weitere Informationen finden Sie im Abschnitt Beseitigung von Komponenten mit IDisposable IAsyncDisposableIDisposableIAsyncDisposable.

Lebenszyklusmethoden der Basisklasse

Beim Überschreiben der Lebenszyklusmethoden von Blazor ist es nicht erforderlich, Lebenszyklusmethoden der Basisklasse für ComponentBase aufzurufen. Eine Komponente sollte jedoch in den folgenden Situationen eine überschriebene Basisklassenlebenszyklusmethode aufrufen:

  • Beim Überschreiben von ComponentBase.SetParametersAsync wird in der Regel await base.SetParametersAsync(parameters); aufgerufen, da die Basisklassenmethode andere Lebenszyklusmethoden aufruft und das Rendern in komplexer Weise auslöst. Weitere Informationen finden Sie im Abschnitt Wenn Parameter festgelegt sind (SetParametersAsync).
  • Wenn die Basisklassenmethode Logik enthält, die ausgeführt werden muss. Bibliotheksconsumer rufen in der Regel Lebenszyklusmethoden der Basisklasse auf, wenn sie eine Basisklasse erben, da Bibliotheksbasisklassen häufig benutzerdefinierte Lebenszykluslogik zum Ausführen haben. Wenn die App eine Basisklasse aus einer Bibliothek verwendet, lesen Sie die Dokumentation der Bibliothek für Anleitung.

Im folgenden Beispiel wird base.OnInitialized(); aufgerufen, um sicherzustellen, dass die OnInitialized-Methode der Basisklasse ausgeführt wird. Ohne den Aufruf wird BlazorRocksBase2.OnInitialized nicht ausgeführt.

BlazorRocks2.razor:

@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<PageTitle>Blazor Rocks!</PageTitle>

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}
@page "/blazor-rocks-2"
@using Microsoft.Extensions.Logging
@inherits BlazorRocksBase2
@inject ILogger<BlazorRocks2> Logger

<h1>Blazor Rocks! Example 2</h1>

<p>
    @BlazorRocksText
</p>

@code {
    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocks2 executed!");

        base.OnInitialized();
    }
}

BlazorRocksBase2.cs:

using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } = "Blazor rocks the browser!";

    protected override void OnInitialized() =>
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;

namespace BlazorSample;

public class BlazorRocksBase2 : ComponentBase
{
    [Inject]
    private ILogger<BlazorRocksBase2> Logger { get; set; } = default!;

    public string BlazorRocksText { get; set; } =
        "Blazor rocks the browser!";

    protected override void OnInitialized()
    {
        Logger.LogInformation("Initialization code of BlazorRocksBase2 executed!");
    }
}

Statusänderungen (StateHasChanged)

StateHasChanged benachrichtigt die Komponente, dass sich ihr Zustand geändert hat. Wenn zutreffend, wird durch den Aufruf von StateHasChanged ein erneutes Rendern ausgelöst, das stattfindet, wenn der Hauptthread der App frei ist.

StateHasChanged wird für EventCallback-Methoden automatisch aufgerufen. Weitere Informationen zu Ereignisrückrufen finden Sie unter Blazor-Ereignisbehandlung in ASP.NET Core.

Weitere Informationen zum Rendern von Komponenten und zum Aufrufen von StateHasChanged, einschließlich des Aufrufs mit ComponentBase.InvokeAsync, finden Sie unter Razor-Komponentenrendering in ASP.NET Core.

Behandeln unvollständiger asynchroner Aktionen beim Rendern

Asynchrone Aktionen, die in Lebenszyklusereignissen ausgeführt werden, sind möglicherweise nicht abgeschlossen, bevor die Komponente gerendert wird. Objekte können während der Ausführung der Lebenszyklusmethode null oder unvollständig mit Daten gefüllt sein. Stellen Sie eine Renderinglogik bereit, um zu bestätigen, dass die Objekte initialisiert sind. Rendern Sie UI-Elemente für Platzhalter (z. B. eine Nachricht zum Ladevorgang), während Objekte null sind.

In der folgenden Komponente wird OnInitializedAsync überschrieben, um asynchron Filmbewertungsdaten (movies) bereitzustellen. Wenn movies gleich null ist, wird dem Benutzer eine Nachricht zum Ladevorgang angezeigt. Nachdem die von OnInitializedAsync zurückgegebene Task abgeschlossen ist, wird die Komponente mit dem aktualisierten Zustand neu gerendert.

<h1>Sci-Fi Movie Ratings</h1>

@if (movies == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <ul>
        @foreach (var movie in movies)
        {
            <li>@movie.Title &mdash; @movie.Rating</li>
        }
    </ul>
}

@code {
    private Movies[]? movies;

    protected override async Task OnInitializedAsync()
    {
        movies = await GetMovieRatings(DateTime.Now);
    }
}

Behandeln von Fehlern

Informationen zum Behandeln von Fehlern während der Ausführung der Lebenszyklusmethode finden Sie unter Behandeln von Fehlern Blazor-Apps in ASP.NET Core.

Zustandsbehaftete erneute Verbindung nach dem Prerendering

Beim Vorabrendern auf dem Server wird eine Komponente zunächst statisch als Teil der Seite gerendert. Sobald der Browser eine SignalR-Verbindung mit dem Server herstellt, wird die Komponente erneut gerendert und ist nun interaktiv. Wenn die OnInitialized{Async}-Lebenszyklusmethode zur Initialisierung der Komponente vorhanden ist, wird die Methode zweimal ausgeführt:

  • Wenn die Komponente statisch vorab gerendert ist.
  • Nachdem die Serververbindung hergestellt wurde.

Dies kann zu einer spürbaren Änderung der auf der Benutzeroberfläche angezeigten Daten führen, wenn die Komponente schließlich gerendert wird. Um dieses Verhalten zu vermeiden, übergeben Sie einen Bezeichner, um den Status während des Vorabrenderns zwischenzuspeichern und den Status nach dem Vorabrendern abzurufen.

Im folgenden Code wird ein WeatherForecastService angezeigt, der die Änderung der Datenanzeige aufgrund des Vorabrenderns vermeidet. Das erwartete Delay-Element (await Task.Delay(...)) simuliert eine kurze Verzögerung, bevor Daten aus der GetForecastAsync-Methode zurückgegeben werden.

Fügen Sie IMemoryCache-Dienste mit AddMemoryCache zur Dienstsammlung in der Program-Datei der App hinzu:

builder.Services.AddMemoryCache();

WeatherForecastService.cs:

using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)
{
    private static readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    public IMemoryCache MemoryCache { get; } = memoryCache;

    public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

namespace BlazorSample;

public class WeatherForecastService(IMemoryCache memoryCache)
{
    private static readonly string[] summaries =
    [
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    ];

    public IMemoryCache MemoryCache { get; } = memoryCache;

    public Task<WeatherForecast[]?> GetForecastAsync(DateOnly startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]?> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using Microsoft.Extensions.Caching.Memory;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = summaries[Random.Shared.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using BlazorSample.Shared;

public class WeatherForecastService
{
    private static readonly string[] summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild",
        "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public WeatherForecastService(IMemoryCache memoryCache)
    {
        MemoryCache = memoryCache;
    }

    public IMemoryCache MemoryCache { get; }

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        return MemoryCache.GetOrCreateAsync(startDate, async e =>
        {
            e.SetOptions(new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow =
                    TimeSpan.FromSeconds(30)
            });

            var rng = new Random();

            await Task.Delay(TimeSpan.FromSeconds(10));

            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = startDate.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = summaries[rng.Next(summaries.Length)]
            }).ToArray();
        });
    }
}

Weitere Informationen zu finden Sie RenderModeunter Leitfaden zu BlazorSignalR in ASP.NET Core.

Der Inhalt des Abschnitts konzentriert sich auf Blazor Web App-Web-Apps und die zustandsbehaftete SignalR-Neuverbindung. Informationen zum Beibehalten des Zustands während der Ausführung von Initialisierungscode während des Prerenderings finden Sie unter Prerendering von Razor-Komponenten in ASP.NET Core.

Obwohl sich der Inhalt in diesem Abschnitt auf Blazor Server und die zustandsbehaftete SignalR-Verbindungswiederherstellung konzentriert, umfasst das Szenario für Vorabrendern in gehosteten Blazor WebAssembly-Lösungen (WebAssemblyPrerendered) ähnliche Bedingungen und Verfahren, um die doppelte Ausführung von Entwicklercode zu verhindern. Informationen zum Beibehalten des Zustands während der Ausführung von Initialisierungscode während des Prerenderings finden Sie unter Prerendering und Integrieren von Razor-Komponenten in ASP.NET Core.

Voarabrendering mit JavaScript-Interop

In diesem Abschnitt werden serverseitige Apps beschrieben, die Razor-Komponenten vorab rendern. Das Vorabrendering wird in Vorabrendern von ASP.NET Core-Razor-Komponenten behandelt.

Hinweis

Interne Navigation für interaktives Routing in Blazor Web Apps beinhaltet keine Anforderung neuer Seiteninhalte vom Server. Daher findet das Vorabrendern nicht für interne Seitenanforderungen statt. Wenn die App interaktives Routing verwendet, führen Sie ein vollständiges Neuladen der Seite für Komponentenbeispiele durch, die das Vorabrendern veranschaulichen. Weitere Informationen finden Sie unter Prerendering von Razor-Komponenten in ASP.NET Core.

In diesem Abschnitt werden serverseitige Anwendungen und gehostete Blazor WebAssembly-Anwendungen beschrieben, die Razor-Komponenten vorab rendern. Informationen zum Prerendering finden Sie in Prerendering und Integrieren von Razor-Komponenten in ASP.NET Core.

Während der Voreinstellung ist das Aufrufen von JavaScript (JS) nicht möglich. Das folgende Beispiel zeigt, wie JS-Interoperabilität als Teil der Initialisierungslogik einer Komponente auf eine Weise verwendet wird, die mit dem Voarabrendering kompatibel ist.

Die folgende -Funktion scrollElementIntoView:

window.scrollElementIntoView = (element) => {
  element.scrollIntoView();
  return element.getBoundingClientRect().top;
}

Wenn IJSRuntime.InvokeAsync die JS-Funktion im Komponentencode aufruft, wird ElementReference nur in OnAfterRenderAsync und nicht in einer früheren Lebenszyklusmethode verwendet, da es kein HTML-DOM-Element gibt, bis die Komponente gerendert wird.

StateHasChanged (Referenzquelle) wird aufgerufen, um die erneute Darstellung der Komponente mit dem neuen Status, der aus dem Aufruf zur JS-Interoperabilität erhalten wurde, in die Warteschlange zu stellen (weitere Informationen finden Sie unter Rendern von ASP.NET Core Razor-Komponenten). Es wird keine Endlosschleife erstellt, da StateHasChanged nur aufgerufen wird, wenn scrollPosition auf null festgelegt wurde.

PrerenderedInterop.razor:

@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<PageTitle>Prerendered Interop</PageTitle>

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}
@page "/prerendered-interop"
@using Microsoft.AspNetCore.Components
@using Microsoft.JSInterop
@inject IJSRuntime JS

<h1>Prerendered Interop Example</h1>

<div @ref="divElement" style="margin-top:2000px">
    Set value via JS interop call: <strong>@scrollPosition</strong>
</div>

@code {
    private ElementReference divElement;
    private double? scrollPosition;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && scrollPosition is null)
        {
            scrollPosition = await JS.InvokeAsync<double>(
                "scrollElementIntoView", divElement);

            StateHasChanged();
        }
    }
}

Im vorherigen Beispiel wird der Client mit einer globalen Funktion belastet. Einen besseren Ansatz für Produktions-Apps finden Sie unter JavaScript-Isolation in JavaScript-Modulen.

Beseitigung von Komponenten mit IDisposable und IAsyncDisposable

Wenn eine Komponente IDisposable oder IAsyncDisposable implementiert, fordert das Framework, dass Ressourcen beseitigt werden, wenn die Komponente aus der Benutzeroberfläche entfernt wird. Verlassen Sie sich nicht auf das genaue Timing, wann diese Methoden ausgeführt werden. Zum Beispiel kann IAsyncDisposable ausgelöst werden, bevor oder nachdem eine Task, die in OnInitalizedAsync erwartet wird, aufgerufen oder beendet wird. Außerdem sollte der Code zur Objektentsorgung nicht davon ausgehen, dass Objekte, die während der Initialisierung oder anderer Lebenszyklusmethoden erstellt wurden, existieren.

Komponenten sollten IDisposable und IAsyncDisposable nicht gleichzeitig implementieren müssen. Wenn beide implementiert werden, führt das Framework nur die asynchrone Überladung aus.

Entwicklercode muss so gestaltet sein, dass IAsyncDisposable-Implementierungen nicht viel Zeit in Anspruch nehmen.

Löschen von Objektverweisen bei JavaScript-Interoperabilität

Alle Beispiele im Artikel zur JavaScript-Interoperabilität (JS) veranschaulichen typische Muster zum Löschen von Objekten:

Objektverweise bei JS-Interoperabilität werden als Zuordnung implementiert, die durch einen Bezeichner auf der Seite des JS-Interoperabilitätsaufrufs definiert ist, der den Verweis erstellt. Wenn die Objektlöschung von .NET oder JS aus initiiert wird, entfernt Blazor den Eintrag aus der Zuordnung, und das Objekt kann durch die Garbage Collection gelöscht werden, sofern kein anderer starker Verweis auf das Objekt vorhanden ist.

Sie sollten immer mindestens die auf .NET-Seite erstellten Objekte löschen, um Verluste im von .NET verwalteten Arbeitsspeicher zu vermeiden.

DOM-Bereinigungsvorgänge während der Beseitigung von Komponenten

Weitere Informationen finden Sie unter JavaScript-Interoperabilität von Blazor in ASP.NET Core (JS-Interoperabilität).

Eine Anleitung zu JSDisconnectedException beim Trennen einer Verbindung finden Sie unter ASP.NET CoreBlazor JavaScript-Interoperabilität (JS-Interop). Allgemeine Anleitungen zur Behandlung von JavaScript-Interopfehlern finden Sie im Abschnitt JavaScript-Interop unter Behandeln von Fehlern in ASP.NET Core Blazor-Apps.

Synchrone IDisposable

Verwenden Sie für synchrone Beseitigungsaufgaben IDisposable.Dispose.

Die folgende Komponente führt folgende Aktionen aus:

  • Implementiert IDisposable mit der @implementsRazor-Direktive.
  • Beseitigt obj (ein Typ, der IDisposable implementiert).
  • Eine NULL-Überprüfung wird ausgeführt, da obj in einer Lebenszyklusmethode erstellt wird (nicht gezeigt).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Wenn ein einzelnes Objekt beseitigt werden muss, kann ein Lambdaausdruck verwendet werden, um das Objekt zu beseitigen, wenn Dispose aufgerufen wird. Das folgende Beispiel wird im Artikel Razor-Komponentenrendering in ASP.NET Core angezeigt und veranschaulicht die Verwendung eines Lambdaausdrucks zur Bereinigung eines Timer-Elements.

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</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();
}

TimerDisposal1.razor:

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</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();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@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();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@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();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@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();
}

CounterWithTimerDisposal1.razor:

@page "/counter-with-timer-disposal-1"
@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();
}

Hinweis

Im vorangegangenen Beispiel wird der Aufruf von StateHasChanged von einem Aufruf von ComponentBase.InvokeAsync umschlossen, da der Rückruf außerhalb des Synchronisierungskontexts von Blazor aufgerufen wird. Weitere Informationen finden Sie unter Rendering von Razor-Komponenten in ASP.NET Core.

Überprüfen Sie , bevor aufgerufen wird, wenn das Objekt in einer Lebenszyklusmethode wie OnInitialized{Async}nullDispose erstellt wird.

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

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

@code {
    private int currentCount = 0;
    private Timer? timer;

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

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

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

TimerDisposal2.razor:

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

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

@code {
    private int currentCount = 0;
    private Timer? timer;

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

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

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

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-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;

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

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

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

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-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;

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

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

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

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-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;

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

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

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

CounterWithTimerDisposal2.razor:

@page "/counter-with-timer-disposal-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;

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

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

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

Weitere Informationen finden Sie unter

Asynchrone IAsyncDisposable

Verwenden Sie für asynchrone Beseitigungsaufgaben IAsyncDisposable.DisposeAsync.

Die folgende Komponente führt folgende Aktionen aus:

  • Implementiert IAsyncDisposable mit der @implementsRazor-Direktive.
  • Beseitigt obj (ein nicht verwalteter Typ, der IAsyncDisposable implementiert).
  • Eine NULL-Überprüfung wird ausgeführt, da obj in einer Lebenszyklusmethode erstellt wird (nicht gezeigt).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Weitere Informationen finden Sie unter

Zuweisung von null zu beseitigten Objekten

In der Regel ist es nicht notwendig, null freigegebenen Objekten zuzuweisen, nachdem Dispose/DisposeAsync aufgerufen wurde. Zu den seltenen Fällen, in denen null zugewiesen werden muss, gehören:

  • Wenn der Typ des Objekts mangelhaft implementiert ist und wiederholte Aufrufe von Dispose/DisposeAsync nicht toleriert werden, müssen Sie null nach der Beseitigung zuweisen, um weitere Aufrufe von Dispose/DisposeAsync kontrolliert zu überspringen.
  • Wenn ein langlebiger Prozess weiterhin einen Verweis auf ein beseitigtes Objekt enthält, kann der Garbage Collector durch Zuweisung von null das Objekt trotz des langlebigen Prozesses, der einen Verweis auf das Objekt enthält, freigeben.

Dies sind ungewöhnliche Szenarien. Für Objekte, die ordnungsgemäß implementiert sind und sich normal verhalten, macht es keinen Sinn, beseitigten Objekten null zuzuweisen. In den seltenen Fällen, in denen null einem Objekt zugewiesen werden muss, wird empfohlen, den entsprechenden Grund zu dokumentieren und nach einer Lösung zu suchen, die vermeidet, dass null zugewiesen werden muss.

StateHasChanged

Hinweis

Das Aufrufen von StateHasChanged in Dispose und DisposeAsync wird nicht unterstützt. StateHasChanged könnte im Rahmen des Beendens des Renderers aufgerufen werden, sodass die Anforderung von UI-Updates an diesem Punkt nicht unterstützt wird.

Ereignishandler

Kündigen Sie die .NET-Ereignisabonnements der Ereignishandler immer. Die folgenden Blazor-Formularbeispiele veranschaulichen die Abbestellung eines Ereignishandlers in der Dispose-Methode:

  • Ansatz mit einem privatem Feld und Lambdaausdruck

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        private EventHandler<FieldChangedEventArgs>? fieldChanged;
    
        protected override void OnInitialized()
        {
            editContext = new(model);
    
            fieldChanged = (_, __) =>
            {
                ...
            };
    
            editContext.OnFieldChanged += fieldChanged;
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= fieldChanged;
        }
    }
    
  • Ansatz mit einer privaten Methode

    @implements IDisposable
    
    <EditForm ... EditContext="editContext" ...>
        ...
        <button type="submit" disabled="@formInvalid">Submit</button>
    </EditForm>
    
    @code {
        ...
    
        protected override void OnInitialized()
        {
            editContext = new(model);
            editContext.OnFieldChanged += HandleFieldChanged;
        }
    
        private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
        {
            ...
        }
    
        public void Dispose()
        {
            editContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
    

Weitere Informationen finden Sie im Abschnitt Beseitigung von Komponenten mit IDisposable und IAsyncDisposable.

Weitere Informationen über die EditForm-Komponente und Formulare finden Sie in der Übersicht über ASP.NET CoreBlazor-Formulare und in den anderen Formularartikeln im Knoten Formulare.

Anonyme Funktionen, Methoden und Ausdrücke

Wenn anonyme Funktionen, Methoden oder Ausdrücke verwendet werden, ist es nicht erforderlich, IDisposable zu implementieren und Delegaten abzubestellen. Es kann jedoch problematisch sein, einen Delegaten abzubestellen, wenn das Objekt, das das Ereignis offenlegt, die Lebensdauer der Komponente überschreitet, die den Delegaten registriert. Wenn dies der Fall ist, führt dies zu einem Arbeitsspeicherverlust, da der registrierte Delegat das ursprüngliche Objekt aufrecht erhält. Verwenden Sie daher nur die folgenden Ansätze, wenn Sie wissen, dass der Ereignisdelegat schnell beseitigt wird. Wenn Sie sich bezüglich der Lebensdauer von Objekten, die eine Bereinigung erfordern, nicht sicher sind, abonnieren Sie eine Delegatmethode, und verwerfen Sie den Delegaten wie in den früheren Beispielen ordnungsgemäß.

  • Anonymer Lambdamethodenansatz (explizite Bereinigung nicht erforderlich):

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        formInvalid = !editContext.Validate();
        StateHasChanged();
    }
    
    protected override void OnInitialized()
    {
        editContext = new(starship);
        editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
    }
    
  • Anonymer Lambdaausdrucksansatz (explizite Bereinigung nicht erforderlich):

    private ValidationMessageStore? messageStore;
    
    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }
    
    protected override void OnInitialized()
    {
        ...
    
        messageStore = new(CurrentEditContext);
    
        CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier);
    }
    

    Das vollständige Beispiel mit dem obigen Code mit anonymen Lambdaausdrücken finden Sie im Artikel -Formulare und -Validierung in ASP.NET Core Blazor.

Weitere Informationen finden Sie unter Bereinigen von nicht verwalteten Ressourcen und den Themen zum Implementieren der Methoden Dispose und DisposeAsync.

Abbrechbare Hintergrundarbeit

Komponenten führen häufig Hintergrundaufgaben aus, die lange dauern, zum Beispiel die Durchführung von Netzwerkaufrufen (HttpClient) und die Interaktion mit Datenbanken. Es ist wünschenswert, die Hintergrundarbeit zu unterbinden, um Systemressourcen in mehreren Situationen zu sparen. Beispielsweise werden asynchrone Hintergrundvorgänge nicht automatisch beendet, wenn ein Benutzer von einer Komponente wegnavigiert.

Andere Gründe, warum Arbeitselemente, die im Hintergrund ausgeführt werden, unterbrochen werden müssen, sind die folgenden:

  • Eine ausgeführte Hintergrundaufgabe wurde mit fehlerhaften Eingabedaten oder Verarbeitungsparametern gestartet.
  • Die aktuellen Arbeitselemente, die im Hintergrund ausgeführt werden, müssen durch neue Arbeitselemente ersetzt werden.
  • Die Priorität der aktuell ausgeführten Aufgaben muss geändert werden.
  • Die App muss für die erneute Serverbereitstellung heruntergefahren werden.
  • Serverressourcen werden eingeschränkt und erfordern die Neuplanung von Arbeitselementen, die im Hintergrund ausgeführt werden.

So implementieren Sie ein abbrechbares Hintergrundarbeitsmuster in einer Komponente:

Im folgenden Beispiel:

  • await Task.Delay(5000, cts.Token); stellt asynchrone Hintergrundaufgaben mit langer Ausführungszeit dar.
  • BackgroundResourceMethod stellt eine Hintergrundmethode mit langer Ausführungszeit dar, die nicht gestartet werden sollte, wenn die Resource vor dem Aufruf der Methode verworfen wird.

BackgroundWork.razor:

@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<PageTitle>Background Work</PageTitle>

<h1>Background Work Example</h1>

<p>
    <button @onclick="LongRunningWork">Trigger long running work</button>
    <button @onclick="Dispose">Trigger Disposal</button>
</p>
<p>Study logged messages in the console.</p>
<p>
    If you trigger disposal within 10 seconds of page load, the 
    <code>BackgroundResourceMethod</code> isn't executed.
</p>
<p>
    If disposal occurs after <code>BackgroundResourceMethod</code> is called but
    before action is taken on the resource, an <code>ObjectDisposedException</code>
    is thrown by <code>BackgroundResourceMethod</code>, and the resource isn't
    processed.
</p>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();
    private IList<string> messages = [];

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(10000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");

        if (!cts.IsCancellationRequested)
        {
            cts.Cancel();
        }
        
        cts?.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose() => disposed = true;
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new();
    private CancellationTokenSource cts = new();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}
@page "/background-work"
@using System.Threading
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject ILogger<BackgroundWork> Logger

<button @onclick="LongRunningWork">Trigger long running work</button>
<button @onclick="Dispose">Trigger Disposal</button>

@code {
    private Resource resource = new Resource();
    private CancellationTokenSource cts = new CancellationTokenSource();

    protected async Task LongRunningWork()
    {
        Logger.LogInformation("Long running work started");

        await Task.Delay(5000, cts.Token);

        cts.Token.ThrowIfCancellationRequested();
        resource.BackgroundResourceMethod(Logger);
    }

    public void Dispose()
    {
        Logger.LogInformation("Executing Dispose");
        cts.Cancel();
        cts.Dispose();
        resource?.Dispose();
    }

    private class Resource : IDisposable
    {
        private bool disposed;

        public void BackgroundResourceMethod(ILogger<BackgroundWork> logger)
        {
            logger.LogInformation("BackgroundResourceMethod: Start method");

            if (disposed)
            {
                logger.LogInformation("BackgroundResourceMethod: Disposed");
                throw new ObjectDisposedException(nameof(Resource));
            }

            // Take action on the Resource

            logger.LogInformation("BackgroundResourceMethod: Action on Resource");
        }

        public void Dispose()
        {
            disposed = true;
        }
    }
}

Blazor Server-Ereignisse zur Wiederherstellung von Verbindungen

Die in diesem Artikel behandelten Ereignisse zum Komponentenlebenszyklus werden getrennt von den Ereignishandlern zur Wiederherstellung von serverseitigen Verbindungen ausgeführt. Wenn die SignalR-Verbindung mit dem Client getrennt wird, werden nur Updates der Benutzeroberfläche unterbrochen. Updates der Benutzeroberfläche werden fortgesetzt, sobald die Verbindung wiederhergestellt wurde. Weitere Informationen zu Verbindungshandlerereignissen und zur Konfiguration finden Sie unter Leitfaden zu BlazorSignalR in ASP.NET Core.

Zusätzliche Ressourcen

Behandeln abgefangener Ausnahmen außerhalb des Lebenszyklus einer Razor-Komponente