Fehlerbehandlung in ASP.NET Core Blazor-Apps

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 beschrieben, wie Blazor Ausnahmefehler behandelt und wie Sie Apps entwickeln können, die Fehler erkennen und behandeln.

Ausführliche Fehler bei der Entwicklung

Wenn eine Blazor-App während der Entwicklung nicht ordnungsgemäß funktioniert, erhalten Sie nun ausführliche Fehlerinformationen von der App, die Sie beim Beheben des Problems unterstützen. Wenn ein Fehler auftritt, zeigen Blazor-Apps eine hellgelbe Leiste am unteren Bildschirmrand an:

  • Während der Entwicklung leitet die Leiste Sie an die Browserkonsole weiter, in der die Ausnahme angezeigt wird.
  • In der Produktion benachrichtigt die Leiste den Benutzer darüber, dass ein Fehler aufgetreten ist, und empfiehlt, die Seite im Browser neu zu laden.

Die Benutzeroberfläche für diese Fehlerbehandlung ist Teil der Blazor-Projektvorlagen. Nicht alle Versionen der Blazor-Projektvorlagen verwenden das data-nosnippet-Attribut, um Browsern zu signalisieren, die Inhalte der Fehlerbenutzeroberfläche nicht zwischenzuspeichern, aber alle Versionen der Blazor-Dokumentation wenden das Attribut an.

In einer Blazor Web App, passen Sie die Erfahrung in der MainLayout-Komponente an. Da das Taghilfsprogramm für Umgebungen (z. B. <environment include="Production">...</environment>) in Razor-Komponenten nicht unterstützt wird, injiziert das folgende Beispiel IHostEnvironment, um Fehlermeldungen für verschiedene Umgebungen zu konfigurieren.

Am Anfang von MainLayout.razor:

@inject IHostEnvironment HostEnvironment

Erstellen oder ändern Sie das UI-Markup für Blazor-Fehler:

<div id="blazor-error-ui" data-nosnippet>
    @if (HostEnvironment.IsProduction())
    {
        <span>An error has occurred.</span>
    }
    else
    {
        <span>An unhandled exception occurred.</span>
    }
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Passen Sie in einer Blazor Server-App die Funktionalität in der Pages/_Host.cshtml-Datei an. Das folgende Beispiel verwendet das Taghilfsprogramm für Umgebungen, um Fehlermeldungen für verschiedene Umgebungen zu konfigurieren.

Passen Sie in einer Blazor Server-App die Funktionalität in der Pages/_Layout.cshtml-Datei an. Das folgende Beispiel verwendet das Taghilfsprogramm für Umgebungen, um Fehlermeldungen für verschiedene Umgebungen zu konfigurieren.

Passen Sie in einer Blazor Server-App die Funktionalität in der Pages/_Host.cshtml-Datei an. Das folgende Beispiel verwendet das Taghilfsprogramm für Umgebungen, um Fehlermeldungen für verschiedene Umgebungen zu konfigurieren.

Erstellen oder ändern Sie das UI-Markup für Blazor-Fehler:

<div id="blazor-error-ui" data-nosnippet>
    <environment include="Staging,Production">
        An error has occurred.
    </environment>
    <environment include="Development">
        An unhandled exception occurred.
    </environment>
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Passen Sie in einer Blazor WebAssembly-App die Darstellung in der wwwroot/index.html-Datei an:

<div id="blazor-error-ui" data-nosnippet>
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Das blazor-error-ui-Element wird normalerweise ausgeblendet, weil der display: none-Stil der blazor-error-ui-CSS-Klasse im automatisch generierten Stylesheet der App vorhanden ist. Wenn ein Fehler auftritt, wird display: block vom Framework auf das Element angewendet.

Das blazor-error-ui-Element ist normalerweise ausgeblendet, weil der display: none-Stil der blazor-error-ui-CSS-Klasse im Stylesheet der Website (im Ordner wwwroot/css) vorhanden ist. Wenn ein Fehler auftritt, wird display: block vom Framework auf das Element angewendet.

Detaillierte Leitungsfehler

Dieser Abschnitt gilt für Blazor Web Apps, die über einen Stromkreis arbeiten.

Dieser Abschnitt gilt für Blazor Server-Apps.

Clientseitige Fehlermeldungen enthalten weder die Aufrufliste noch Details zur Fehlerursache, Serverprotokolle hingegen schon. Zu Entwicklungszwecken können vertrauliche Informationen zu Leitungsfehlern für den Client verfügbar gemacht werden, indem Sie detaillierte Fehlermeldungen aktivieren.

Legen Sie CircuitOptions.DetailedErrors auf true fest. Weitere Informationen und ein Beispiel finden Sie unter Leitfaden zu BlazorSignalR in ASP.NET Core.

Eine Alternative zum Festlegen von CircuitOptions.DetailedErrors besteht darin, den Konfigurationsschlüssel DetailedErrors in der Einstellungsdatei der Development-Umgebung der App (appsettings.Development.json) auf true festzulegen. Legen Sie außerdem die serverseitige SignalR-Protokollierung (Microsoft.AspNetCore.SignalR) zur detaillierten Protokollierung von SignalR auf Debug oder Trace fest.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

Der Konfigurationsschlüssel DetailedErrors kann auch auf true festgelegt werden, indem auf Development/Staging-Umgebungsservern oder auf dem lokalen System die ASPNETCORE_DETAILEDERRORS-Umgebungsvariable mit dem Wert true verwendet wird.

Warnung

Vermeiden Sie es grundsätzlich, Fehlerinformationen für Internetclients verfügbar zu machen. Dies stellt ein Sicherheitsrisiko dar.

Detaillierte Fehler für Razor serverseitiges Rendern von Komponenten

Dieser Abschnitt gilt für Blazor Web Apps.

Verwenden Sie die RazorComponentsServiceOptions.DetailedErrors Option, um die Erstellung detaillierter Informationen zu Fehlern für das serverseitige Rendern von Razor Komponenten zu steuern. Der Standardwert ist false.

Im folgenden Beispiel werden detaillierte Fehler aktiviert:

builder.Services.AddRazorComponents(options => 
    options.DetailedErrors = builder.Environment.IsDevelopment());

Warnung

Aktivieren Sie nur detaillierte Fehler in der Development-Umgebung. Detaillierte Fehler können vertrauliche Informationen über die App enthalten, die Angreifer sich zunutze machen können.

Das vorangehende Beispiel bietet einen gewissen Grad an Sicherheit, indem der Wert von DetailedErrors basierend auf dem von IsDevelopment zurückgegebenen Wert festgelegt wird. Wenn sich die App in der Development-Umgebung befindet, wird DetailedErrors auf true festgelegt. Dieser Ansatz ist nicht absolut sicher, da es möglich ist, eine Produktions-App auf einem öffentlichen Server in der Development-Umgebung zu hosten.

Behandeln von Ausnahmefehlern in Entwicklercode

Damit die Ausführung einer App nach einem Fehler fortgesetzt werden kann, muss die App eine Fehlerbehandlungslogik enthalten. In späteren Abschnitten dieses Artikels werden mögliche Quellen für Ausnahmefehler beschrieben.

Rendern Sie in einer Produktionsumgebung keine Ausnahmemeldungen des Frameworks oder Stapelüberwachungen in der Benutzeroberfläche. Durch Rendern von Ausnahmemeldungen oder Stapelüberwachungen kann Folgendes geschehen:

  • Vertrauliche Informationen werden gegenüber Endbenutzern offengelegt.
  • Böswilligen Benutzern wird geholfen, Schwachstellen in einer App zu erkennen, die die Sicherheit von Server, App oder Netzwerk beeinträchtigen können.

Ausnahmefehler für Leitungen

Dieser Abschnitt gilt für serverseitige Apps, die über eine Verbindung betrieben werden.

Razor-Komponenten mit aktivierter Serverinteraktivität sind auf dem Server zustandsbehaftet. Während die Benutzer*innen mit der Komponente auf dem Server interagieren, unterhalten sie eine Verbindung mit dem Server, die als Leitung bezeichnet wird. Die Leitung enthält aktive Instanzen von Komponenten und weist zahlreiche andere Aspekte zum Zustand auf wie:

  • Die zuletzt gerenderte Ausgabe von Komponenten
  • Die aktuellen Delegate zur Ereignisbehandlung, die durch clientseitige Ereignisse ausgelöst werden können

Wenn ein Benutzer die App in mehreren Registerkarten eines Browsers öffnet, werden mehrere unabhängige Leitungen erstellt.

Die meisten Ausnahmefehler werden von Blazor als für die Leitung schwerwiegende Fehler behandelt. Wenn eine Leitung aufgrund eines Ausnahmefehlers beendet wird, kann der Benutzer nur dann mit der App weiter interagieren, wenn die Seite nochmals geladen und so eine neue Leitung erstellt wird. Leitungen außerhalb der beendeten Leitung, bei denen es sich um Leitungen für andere Benutzer oder andere Browserregisterkarten handelt, sind davon nicht betroffen. Dieses Szenario ähnelt einer Desktop-App, die abstürzt. Die abgestürzte App muss neu gestartet werden, aber andere Apps sind nicht betroffen.

Eine Leitung wird durch das Framework beendet, wenn aus folgenden Gründen ein Ausnahmefehler auftritt:

  • Bei einem Ausnahmefehler wird die Leitung häufig in einen nicht definierten Zustand versetzt.
  • Nach einem Ausnahmefehler kann ein normales Funktionieren der App nicht mehr sichergestellt werden.
  • Wenn die Leitung weiter in einem nicht definierten Zustand bestehen bleibt, können für die App Sicherheitsrisiken bestehen.

Globale Ausnahmebehandlung

Ansätze zur globalen Behandlung von Ausnahmen finden Sie in den folgenden Abschnitten:

Fehlergrenzen

Fehlergrenzen bieten eine gut geeignete Möglichkeit für die Behandlung von Ausnahmen. Die Komponente ErrorBoundary:

  • Rendert den untergeordneten Inhalt, wenn kein Fehler aufgetreten ist.
  • Rendert die Fehler-UI, wenn eine unbehandelte Ausnahme von einer beliebigen Komponente innerhalb der Fehlergrenze ausgelöst wird.

Zum Definieren einer Fehlergrenze verwenden Sie die ErrorBoundary-Komponente, um andere Komponenten zu umschließen. Die Fehlergrenze verwaltet unbehandelte Ausnahmen, die von den umschlossenen Komponenten ausgelöst werden.

<ErrorBoundary>
    ...
</ErrorBoundary>

Um eine Fehlergrenze global zu implementieren, fügen Sie die Grenze um den Textinhalt des Hauptlayouts der App hinzu.

In MainLayout.razor:

<article class="content px-4">
    <ErrorBoundary>
        @Body
    </ErrorBoundary>
</article>

Bei Blazor Web Apps, bei denen die Fehlerbegrenzung nur auf eine statische MainLayout-Komponente angewendet wird, ist die Begrenzung nur während des statischen serverseitigen Renderings (static SSR) aktiv. Die Grenze wird nicht aktiviert, nur weil eine Komponente weiter unten in der Komponentenhierarchie interaktiv ist.

Auf die MainLayout-Komponente kann kein interaktiver Rendermodus angewandt werden, da der Parameter Body der Komponente ein RenderFragment-Delegat ist, der als beliebiger Code nicht serialisiert werden kann. Um Interaktivität für die MainLayout-Komponente und die rest-Komponenten weiter unten in der Komponentenhierarchie zu ermöglichen, muss die Anwendung einen globalen interaktiven Rendering-Modus annehmen, indem der interaktive Rendering-Modus auf die HeadOutlet- und Routes-Komponenteninstanzen in der Stammkomponente der Anwendung angewendet wird, die normalerweise die App-Komponente ist. Im folgenden Beispiel wird der Rendermodus für interaktive Server (InteractiveServer) global verwendet.

In Components/App.razor:

<HeadOutlet @rendermode="InteractiveServer" />

...

<Routes @rendermode="InteractiveServer" />

Wenn Sie lieber keine globale Interaktivität aktivieren möchten, platzieren Sie die Fehlergrenze weiter unten in der Komponentenhierarchie. Unabhängig von der Position der Fehlergrenze sollten Sie folgende wichtige Konzepte berücksichtigen:

  • Wenn die Komponente, an der die Fehlergrenze platziert wurde, nicht interaktiv ist, kann die Fehlergrenze nur während statischem SSR auf dem Server aktiviert werden. Beispielsweise kann die Grenze aktiviert werden, wenn ein Fehler in einer Komponenten-Lebenszyklusmethode ausgelöst wird, aber nicht für ein Ereignis, das durch Benutzerinteraktivität innerhalb der Komponente ausgelöst wird, z. B. ein Fehler, der von einem Schaltflächenklickhandler ausgelöst wird.
  • Wenn die Komponente, an der die Fehlergrenze platziert wurde, interaktiv ist, kann die Fehlergrenze für interaktive Komponenten aktiviert werden, die sie umschließt.

Hinweis

Die vorstehenden Überlegungen sind für eigenständige Blazor WebAssembly-Apps nicht relevant, da das clientseitige Rendering (CSR) von Blazor WebAssembly-Apps vollständig interaktiv ist.

Betrachten Sie das folgende Beispiel, bei dem eine Ausnahme, die von einer eingebetteten Leistungsindikatorkomponente ausgelöst wird, von einer Fehlergrenze in der Home-Komponente abgefangen wird, die einen interaktiven Rendermodus verwendet.

EmbeddedCounter.razor:

<h1>Embedded Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;

        if (currentCount > 5)
        {
            throw new InvalidOperationException("Current count is too big!");
        }
    }
}

Home.razor:

@page "/"
@rendermode InteractiveServer

<PageTitle>Home</PageTitle>

<h1>Home</h1>

<ErrorBoundary>
    <EmbeddedCounter />
</ErrorBoundary>

Betrachten Sie das folgende Beispiel, bei dem eine Ausnahme, die von einer eingebetteten Leistungsindikatorkomponente ausgelöst wird, von einer Fehlergrenze in der Home-Komponente abgefangen wird.

EmbeddedCounter.razor:

<h1>Embedded Counter</h1>

<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;

        if (currentCount > 5)
        {
            throw new InvalidOperationException("Current count is too big!");
        }
    }
}

Home.razor:

@page "/"

<PageTitle>Home</PageTitle>

<h1>Home</h1>

<ErrorBoundary>
    <EmbeddedCounter />
</ErrorBoundary>

Wenn die nicht behandelte Ausnahme für currentCount größer fünf ausgelöst wird, gilt:

  • Der Fehler wird normal protokolliert (System.InvalidOperationException: Current count is too big!).
  • Die ausgelöste Ausnahme wird von der Fehlergrenze verarbeitet.
  • Die Standard-Fehlerbenutzeroberfläche wird durch die Fehlergrenze gerendert.

Standardmäßig rendert die ErrorBoundary-Komponente für den Fehlerinhalt ein leeres <div>-Element mit der CSS-Klasse blazor-error-boundary. Farben, Text und Symbol für die Standardbenutzeroberfläche werden über das Stylesheet der App im Ordner wwwroot definiert, sodass Sie die Fehlerbenutzeroberfläche beliebig anpassen können.

Die Standard-Fehlerbenutzeroberfläche, die von einer Fehlergrenze gerendert wird. Diese weist einen roten Hintergrund, den Text „Ein Fehler ist aufgetreten“ und ein gelbes Vorsichtssymbol mit einem Ausrufezeichen auf.

So ändern Sie den Standardfehlerinhalt

  • Umschließen Sie die Komponenten der Fehlergrenze in der ChildContent-Eigenschaft.
  • Legen Sie die ErrorContent-Eigenschaft auf den Fehlerinhalt fest.

Im folgenden Beispiel wird die EmbeddedCounter-Komponente umschlossen, und es werden benutzerdefinierte Fehlerinhalte bereitgestellt:

<ErrorBoundary>
    <ChildContent>
        <EmbeddedCounter />
    </ChildContent>
    <ErrorContent>
        <p class="errorUI">😈 A rotten gremlin got us. Sorry!</p>
    </ErrorContent>
</ErrorBoundary>

Im vorherigen Beispiel wird davon ausgegangen, dass das Stylesheet der App eine CSS-Klasse errorUI zum Formatieren des Inhalts enthält. Der Fehlerinhalt wird aus der ErrorContent-Eigenschaft ohne ein Element auf Blockebene gerendert. Ein Element auf Blockebene, z. B. eine Unterteilung (<div>) oder ein Absatzelement (<p>), kann das Fehlerinhaltsmarkup umschließen, ist jedoch nicht erforderlich.

Optional kann der Kontext (@context) des ErrorContent verwendet werden, um Fehlerdaten zu erhalten:

<ErrorContent>
    @context.HelpLink
</ErrorContent>

Der ErrorContent kann auch den Kontext benennen. Im folgenden Beispiel heißt der Kontext exception:

<ErrorContent Context="exception">
    @exception.HelpLink
</ErrorContent>

Warnung

Vermeiden Sie es grundsätzlich, Fehlerinformationen für Internetclients verfügbar zu machen. Dies stellt ein Sicherheitsrisiko dar.

Wenn die Fehlergrenze im Layout der App definiert ist, wird die Fehlerbenutzeroberfläche unabhängig davon angezeigt, zu welcher Seite die Benutzer nach Auftreten des Fehlers navigieren. In den meisten Szenarien wird empfohlen, einen engen Bereich für die Fehlergrenze festzulegen. Wenn Sie den Bereich für die Fehlergrenze weit festlegen, können Sie sie bei nachfolgenden Seitennavigationsereignissen auf einen fehlerfreien Zustand zurücksetzen, indem Sie die Recover-Methode der Fehlergrenze aufrufen.

In MainLayout.razor:

...

<ErrorBoundary @ref="errorBoundary">
    @Body
</ErrorBoundary>

...

@code {
    private ErrorBoundary? errorBoundary;

    protected override void OnParametersSet()
    {
        errorBoundary?.Recover();
    }
}

Zur Vermeidung einer Endlosschleife, bei der die Wiederherstellung eine Komponente lediglich neu rendert und diese den Fehler erneut auslöst, sollten Sie Recover nicht aus der Renderinglogik aufrufen. Rufen Sie Recover nur in diesen Fällen auf:

  • Die Benutzer*innen zeigen durch Ausführung einer Benutzeroberflächengeste (z. B. Auswahl einer Schaltfläche) an, dass sie einen Vorgang wiederholen möchten, oder die Benutzer*innen navigieren zu einer neuen Komponente.
  • Die Ausnahme wird auch durch zusätzlich ausgeführte Logik beseitigt. Wenn die Komponente neu gerendert wird, tritt der Fehler nicht mehr auf.

Im folgenden Beispiel lässt sich die Ausnahme mit einer Schaltfläche beheben:

<ErrorBoundary @ref="errorBoundary">
    <ChildContent>
        <EmbeddedCounter />
    </ChildContent>
    <ErrorContent>
        <div class="alert alert-danger" role="alert">
            <p class="fs-3 fw-bold">😈 A rotten gremlin got us. Sorry!</p>
            <p>@context.HelpLink</p>
            <button class="btn btn-info" @onclick="_ => errorBoundary?.Recover()">
                Clear
            </button>
        </div>
    </ErrorContent>
</ErrorBoundary>

@code {
    private ErrorBoundary? errorBoundary;
}

Sie können auch die Unterklasse ErrorBoundary für die benutzerdefinierte Verarbeitung durch Überschreiben von OnErrorAsync verwenden. Im folgenden Beispiel wird lediglich der Fehler protokolliert, Sie können jedoch jeden gewünschten Fehlerbehandlungscode implementieren. Sie können die Zeile entfernen, die CompletedTask zurückgibt, wenn ihr Code auf eine asynchrone Aufgabe wartet.

CustomErrorBoundary.razor:

@inherits ErrorBoundary
@inject ILogger<CustomErrorBoundary> Logger

@if (CurrentException is null)
{
    @ChildContent
}
else if (ErrorContent is not null)
{
    @ErrorContent(CurrentException)
}

@code {
    protected override Task OnErrorAsync(Exception ex)
    {
        Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
        return Task.CompletedTask;
    }
}

Das vorangehende Beispiel kann auch als Klasse implementiert werden.

CustomErrorBoundary.cs:

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace BlazorSample;

public class CustomErrorBoundary : ErrorBoundary
{
    [Inject]
    ILogger<CustomErrorBoundary> Logger {  get; set; } = default!;

    protected override Task OnErrorAsync(Exception ex)
    {
        Logger.LogError(ex, "😈 A rotten gremlin got us. Sorry!");
        return Task.CompletedTask;
    }
}

Eine der vorherigen Implementierungen, die in einer Komponente verwendet werden:

<CustomErrorBoundary>
    ...
</CustomErrorBoundary>

Alternative globale Ausnahmebehandlung

Der in diesem Abschnitt beschriebene Ansatz gilt für Blazor Server, Blazor WebAssembly und Blazor Web App, die einen globalen interaktiven Rendermodus verwenden (InteractiveServer, InteractiveWebAssembly oder InteractiveAuto). Der Ansatz funktioniert nicht mit Blazor Web Apps, die Rendering-Modi pro Seite/Komponente oder statisches Server-seitiges Rendering (static SSR) verwenden, da der Ansatz auf CascadingValue/CascadingParameter beruht, die nicht über Rendering-Modus-Grenzen hinweg oder mit Komponenten funktionieren, die statisches SSR verwenden.

Eine Alternative zur Verwendung von Fehlergrenzen (ErrorBoundary) besteht darin, eine benutzerdefinierte Fehlerkomponente als CascadingValue an untergeordnete Komponenten zu übergeben. Ein Vorteil der Verwendung einer Komponente gegenüber der Verwendung eines eingefügten Diensts oder der Implementierung einer benutzerdefinierten Protokollierung besteht darin, dass eine kaskadierenden Komponente Inhalte rendern und CSS-Stile anwenden kann, wenn ein Fehler auftritt.

Im folgenden Beispiel einer ProcessError-Komponente werden Fehler lediglich protokolliert, Methoden der Komponente können Fehler jedoch in beliebiger Weise je nach Anforderungen der App verarbeiten, auch mithilfe mehrerer Fehlerverarbeitungsmethoden.

ProcessError.razor:

@inject ILogger<ProcessError> Logger

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public void LogError(Exception ex)
    {
        Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);

        // Call StateHasChanged if LogError directly participates in 
        // rendering. If LogError only logs or records the error,
        // there's no need to call StateHasChanged.
        //StateHasChanged();
    }
}

Hinweis

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

Wenn Sie diesen Ansatz in einem Blazor Web App verwenden, öffnen Sie die Komponente Routes und verpacken Sie die Komponente Router (<Router>...</Router>) mit der Komponente ProcessError. Dies erlaubt der ProcessError-Komponente eine Kaskadierung an beliebige Komponenten der App, wobei die ProcessError-Komponente als CascadingParameter empfangen wird.

In Routes.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

Wenn Sie diesen Ansatz in einer Blazor Server- oder Blazor WebAssembly-App verwenden, öffnen Sie die App-Komponente, und umschließen Sie die Router-Komponente (<Router>...</Router>) mit der ProcessError-Komponente. Dies erlaubt der ProcessError-Komponente eine Kaskadierung an beliebige Komponenten der App, wobei die ProcessError-Komponente als CascadingParameter empfangen wird.

In App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

So verarbeiten Sie Fehler in einer-Komponente:

  • Legen Sie die ProcessError-Komponente im @code-Block als CascadingParameter fest: Fügen Sie in einer Counter-Beispielkomponente in einer App, die auf einer Blazor-Projektvorlage basiert, die folgende ProcessError-Eigenschaft hinzu:

    [CascadingParameter]
    public ProcessError? ProcessError { get; set; }
    
  • Rufen Sie eine Fehlerverarbeitungsmethode mit einem entsprechenden Ausnahmetyp in einem beliebigen catch-Block auf. Die ProcessError-Komponente aus dem Beispiel verfügt nur über eine einzige LogError-Methode, die Fehlerverarbeitungskomponente kann jedoch eine beliebige Anzahl von Fehlerverarbeitungsmethoden bereitstellen, um Anforderungen an eine alternative Fehlerverarbeitung für die gesamte App zu ermöglichen. Das folgende @code-Blockbeispiel mit Counter-Komponente enthält den Cascading-Parameter „ProcessError“ und fängt eine Ausnahme für die Protokollierung ein, wenn die Anzahl größer als fünf ist:

    @code {
        private int currentCount = 0;
    
        [CascadingParameter]
        public ProcessError? ProcessError { get; set; }
    
        private void IncrementCount()
        {
            try
            {
                currentCount++;
    
                if (currentCount > 5)
                {
                    throw new InvalidOperationException("Current count is over five!");
                }
            }
            catch (Exception ex)
            {
                ProcessError?.LogError(ex);
            }
        }
    }
    

Der protokollierte Fehler:

fail: {COMPONENT NAMESPACE}.ProcessError[0]
ProcessError.LogError: System.InvalidOperationException Message: Current count is over five!

Wenn die LogError-Methode direkt an einem Rendering beteiligt ist, z. B. wenn eine benutzerdefinierte Fehlermeldungsleiste angezeigt wird oder die CSS-Stile der gerenderten Elemente geändert werden, wird StateHasChanged am Ende der LogError-Methode aufgerufen, um die Benutzeroberfläche neu zu rendern.

Da bei den Ansätzen in diesem Abschnitt Fehler mit einer try-catch-Anweisung behandelt werden, wird die SignalR-Verbindung einer App zwischen Client und Server nicht getrennt, wenn ein Fehler auftritt, und die Leitung wird aufrechterhalten. Andere nicht behandelte Ausnahmefehler bleiben für eine Leitung schwerwiegend. Weitere Informationen finden Sie im vorherigen Abschnitt, in dem erläutert wird, wie eine Leitung auf Ausnahmefehler reagiert.

Eine App kann eine Fehlerverarbeitungskomponente als kaskadierenden Wert verwenden, um Fehler zentral zu verarbeiten.

Die folgende ProcessError-Komponente übergibt sich selbst als CascadingValue an untergeordnete Komponenten. Im folgenden Beispiel wird der Fehler lediglich protokolliert, Methoden der Komponente können Fehler jedoch in beliebiger Weise je nach Anforderungen der App verarbeiten, auch mithilfe mehrerer Fehlerverarbeitungsmethoden. Ein Vorteil der Verwendung einer Komponente gegenüber der Verwendung eines eingefügten Diensts oder der Implementierung einer benutzerdefinierten Protokollierung besteht darin, dass eine kaskadierenden Komponente Inhalte rendern und CSS-Stile anwenden kann, wenn ein Fehler auftritt.

ProcessError.razor:

@using Microsoft.Extensions.Logging
@inject ILogger<ProcessError> Logger

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {
    [Parameter]
    public RenderFragment ChildContent { get; set; }

    public void LogError(Exception ex)
    {
        Logger.LogError("ProcessError.LogError: {Type} Message: {Message}", 
            ex.GetType(), ex.Message);
    }
}

Hinweis

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

Erstellen Sie in der App-Komponente mit der ProcessError-Komponente einen Wrapper für die Router-Komponente. Dies erlaubt der ProcessError-Komponente eine Kaskadierung an beliebige Komponenten der App, wobei die ProcessError-Komponente als CascadingParameter empfangen wird.

App.razor:

<ProcessError>
    <Router ...>
        ...
    </Router>
</ProcessError>

So verarbeiten Sie Fehler in einer-Komponente:

  • Legen Sie die ProcessError-Komponente im @code-Block als CascadingParameter fest:

    [CascadingParameter]
    public ProcessError ProcessError { get; set; }
    
  • Rufen Sie eine Fehlerverarbeitungsmethode mit einem entsprechenden Ausnahmetyp in einem beliebigen catch-Block auf. Die ProcessError-Komponente aus dem Beispiel verfügt nur über eine einzige LogError-Methode, die Fehlerverarbeitungskomponente kann jedoch eine beliebige Anzahl von Fehlerverarbeitungsmethoden bereitstellen, um Anforderungen an eine alternative Fehlerverarbeitung für die gesamte App zu ermöglichen.

    try
    {
        ...
    }
    catch (Exception ex)
    {
        ProcessError.LogError(ex);
    }
    

Bei Verwendung der ProcessError-Komponente und der LogError-Methode aus dem vorherigen Beispiel wird in Konsole mit den Entwicklertools des Browsers der aufgefangene protokollierte Fehler angegeben:

fail: {COMPONENT NAMESPACE}.Shared.ProcessError[0]
ProcessError.LogError: System.NullReferenceException Message: Object reference not set to an instance of an object.

Wenn die LogError-Methode direkt an einem Rendering beteiligt ist, z. B. wenn eine benutzerdefinierte Fehlermeldungsleiste angezeigt oder die CSS-Stile der gerenderten Elemente geändert werden, wird StateHasChanged am Ende der LogError-Methode aufgerufen, um die Benutzeroberfläche zu rendern.

Da bei den Ansätzen in diesem Abschnitt Fehler mit einer try-catch-Anweisung behandelt werden, wird die SignalR-Verbindung einer Blazor-App zwischen Client und Server nicht getrennt, wenn ein Fehler auftritt, und die Leitung wird aufrechterhalten. Unbehandelte Ausnahmen führen zu einem schwerwiegenden Leitungsfehler. Weitere Informationen finden Sie im vorherigen Abschnitt, in dem erläutert wird, wie eine Leitung auf Ausnahmefehler reagiert.

Protokollieren von Fehlern bei einem permanenten Anbieter

Im Fall eines Ausnahmefehlers wird die Ausnahme in den im Dienstcontainer konfiguriertenILogger-Instanzen protokolliert. Blazor-Apps protokollieren standardmäßig in die Konsolenausgabe beim Konsolenprotokollierungsanbieter. Überlegen Sie, ob Sie die Protokollierung an einem Speicherort auf dem Server (oder einer Back-End-Web-API für clientseitige Apps) mit einem Anbieter durchführen, der die Protokollgröße und die Protokollrotation verwaltet. Alternativ kann die App einen APM-Dienst (Application Performance Management) verwenden, z. B. Azure Application Insights (Azure Monitor).

Hinweis

Native Application Insights-Features für die Unterstützung clientseitiger Apps und die native Unterstützung des Blazor-Frameworks für Google Analytics werden möglicherweise in zukünftigen Releases dieser Technologien zur Verfügung gestellt. Weitere Informationen finden Sie unter Support App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) und Web analytics and diagnostics (enthält Links zu Implementierungen der Community) (dotnet/aspnetcore #5461). In der Zwischenzeit kann eine clientseitige App das Application Insights JavaScript SDK mit JSInterop verwenden, um Fehler aus einer clientseitigen App direkt in Application Insights zu protokollieren.

Während der Entwicklung in einer Blazor-App, die über eine Leitung betrieben wird, sendet die App zur Unterstützung des Debuggings in der Regel alle Ausnahmedetails an die Konsole des Browsers. In der Produktionsumgebung werden keine ausführlichen Fehler an Clients gesendet, die vollständigen Details der Ausnahme werden jedoch auf dem Server protokolliert.

Sie müssen entscheiden, welche Vorfälle protokolliert werden sollen. Zudem müssen Sie den Schweregrad für protokollierte Vorfälle festlegen. Feindliche Benutzer können Fehler möglicherweise absichtlich auslöst. Protokollieren Sie beispielsweise keinen Vorfall aus einem Fehler, bei dem eine unbekannte ProductId in der URL einer Komponente angegeben wurde, über die Produktdetails angezeigt werden. Nicht alle Fehler sollten als zu protokollierende Vorfälle behandelt werden.

Weitere Informationen finden Sie in den folgenden Artikeln:

‡Betrifft serverseitige Blazor-Apps und andere serverseitige ASP.NET Core-Apps, die als Web-API-Back-End-Apps für Blazor dienen. Clientseitige Apps können Fehlerinformationen auf dem Client abfangen und an eine Web-API senden, die die Fehlerinformationen in einem persistenten Protokollierungsanbieter protokolliert.

Im Fall eines Ausnahmefehlers wird die Ausnahme in den im Dienstcontainer konfiguriertenILogger-Instanzen protokolliert. Blazor-Apps protokollieren standardmäßig in die Konsolenausgabe beim Konsolenprotokollierungsanbieter. Sie sollten einen dauerhaften Speicherort auf dem Server für die Protokollierung verwenden, indem Sie Fehlerinformationen an eine Back-End-Web-API senden, die einen Protokollierungsanbieter mit Verwaltung der Protokollgröße und Protokollrotation verwendet. Alternativ kann die Back-End-Web-API-App einen APM-Dienst (Application Performance Management) verwenden, z. B. Azure Application Insights (Azure Monitor)†, um Fehlerinformationen aufzuzeichnen, die von Clients empfangen werden.

Sie müssen entscheiden, welche Vorfälle protokolliert werden sollen. Zudem müssen Sie den Schweregrad für protokollierte Vorfälle festlegen. Feindliche Benutzer können Fehler möglicherweise absichtlich auslöst. Protokollieren Sie beispielsweise keinen Vorfall aus einem Fehler, bei dem eine unbekannte ProductId in der URL einer Komponente angegeben wurde, über die Produktdetails angezeigt werden. Nicht alle Fehler sollten als zu protokollierende Vorfälle behandelt werden.

Weitere Informationen finden Sie in den folgenden Artikeln:

†Native Application Insights-Features für die Unterstützung clientseitiger Apps und die native Unterstützung des Blazor-Frameworks für Google Analytics werden möglicherweise in zukünftigen Releases dieser Technologien zur Verfügung gestellt. Weitere Informationen finden Sie unter Support App Insights in Blazor WASM Client Side (microsoft/ApplicationInsights-dotnet #2143) und Web analytics and diagnostics (enthält Links zu Implementierungen der Community) (dotnet/aspnetcore #5461). In der Zwischenzeit kann eine clientseitige App das Application Insights JavaScript SDK mit JSInterop verwenden, um Fehler aus einer clientseitigen App direkt in Application Insights zu protokollieren.

‡Betrifft serverseitige ASP.NET Core-Apps, die als Web-API-Back-End-Apps für Blazor-Apps dienen. Clientseitige Apps fangen Fehlerinformationen ab und senden sie an eine Web-API, die die Fehlerinformationen in einem persistenten Protokollierungsanbieter protokolliert.

Stellen, an denen Fehler auftreten können

Frameworks und App-Code können nicht behandelte Ausnahmen an einem der folgenden Orte auslösen, die in den folgenden Abschnitten dieses Artikels erläutert werden:

Komponenteninstanziierung

Wenn durch Blazor eine Instanz einer Komponente erstellt wird:

  • Wird der Konstruktor der Komponente aufgerufen.
  • Werden die Konstruktoren von Abhängigkeitsinjektionsdiensten aufgerufen, die über die @inject-Anweisung oder das [Inject]-Attribut für den Konstruktor der Komponente bereitgestellt werden.

Ein Fehler in einem ausgeführten Konstruktor oder Setter für eine beliebige [Inject]-Eigenschaft führt zu einer nicht behandelten Ausnahme und verhindert ein Instanziieren der Komponente durch das Framework. Wenn die App über eine Leitung betrieben wird, schlägt die Leitung fehl. Wenn eine Konstruktorlogik Ausnahmen auslösen kann, muss die App die Ausnahmen mithilfe einer try-catch-Anweisung mit Fehlerbehandlung und Fehlerprotokollierung abfangen.

Lebenszyklusmethoden

Während der Lebensdauer einer Komponente ruft BlazorLebenszyklusmethoden auf. Wenn eine Lebenszyklusmethode synchron oder asynchron eine Ausnahme auslöst, ist die Ausnahme für eine -Leitung schwerwiegend. Damit Komponenten Fehler in Lebenszyklusmethoden behandeln können, fügen Sie eine Fehlerbehandlungslogik hinzu.

Im folgenden Beispiel, in dem OnParametersSetAsync eine Methode zum Aufrufen eines Produkts aufruft:

  • Wird eine Ausnahme, die in der ProductRepository.GetProductByIdAsync-Methode ausgelöst wird, durch eine try-catch-Anweisung behandelt.
  • Wenn der catch-Block ausgeführt wird:
    • wird loadFailed auf true festgelegt, sodass dem Benutzer eine Fehlermeldung angezeigt wird.
    • wird der Fehler protokolliert.
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)
{
    <h2>@details.ProductName</h2>
    <p>
        @details.Description
        <a href="@details.Url">Company Link</a>
    </p>
    
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await Product.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
        public string? Url { get; set; }
    }

    /*
    * Register the service in Program.cs:
    * using static BlazorSample.Components.Pages.ProductDetails;
    * builder.Services.AddScoped<IProductRepository, ProductRepository>();
    */

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id)
        {
            return Task.FromResult(
                new ProductDetail()
                {
                    ProductName = "Flowbee ",
                    Description = "The Revolutionary Haircutting System You've Come to Love!",
                    Url = "https://flowbee.com/"
                });
        }
    }
}
@page "/product-details/{ProductId:int?}"
@inject ILogger<ProductDetails> Logger
@inject IProductRepository Product

<PageTitle>Product Details</PageTitle>

<h1>Product Details Example</h1>

@if (details != null)
{
    <h2>@details.ProductName</h2>
    <p>
        @details.Description
        <a href="@details.Url">Company Link</a>
    </p>
    
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await Product.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
        public string? Url { get; set; }
    }

    /*
    * Register the service in Program.cs:
    * using static BlazorSample.Components.Pages.ProductDetails;
    * builder.Services.AddScoped<IProductRepository, ProductRepository>();
    */

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }

    public class ProductRepository : IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id)
        {
            return Task.FromResult(
                new ProductDetail()
                {
                    ProductName = "Flowbee ",
                    Description = "The Revolutionary Haircutting System You've Come to Love!",
                    Url = "https://flowbee.com/"
                });
        }
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail? details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string? ProductName { get; set; }
        public string? Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}
@page "/product-details/{ProductId:int}"
@using Microsoft.Extensions.Logging
@inject ILogger<ProductDetails> Logger
@inject IProductRepository ProductRepository

@if (details != null)
{
    <h1>@details.ProductName</h1>
    <p>@details.Description</p>
}
else if (loadFailed)
{
    <h1>Sorry, we could not load this product due to an error.</h1>
}
else
{
    <h1>Loading...</h1>
}

@code {
    private ProductDetail details;
    private bool loadFailed;

    [Parameter]
    public int ProductId { get; set; }

    protected override async Task OnParametersSetAsync()
    {
        try
        {
            loadFailed = false;

            // Reset details to null to display the loading indicator
            details = null;

            details = await ProductRepository.GetProductByIdAsync(ProductId);
        }
        catch (Exception ex)
        {
            loadFailed = true;
            Logger.LogWarning(ex, "Failed to load product {ProductId}", ProductId);
        }
    }

    public class ProductDetail
    {
        public string ProductName { get; set; }
        public string Description { get; set; }
    }

    public interface IProductRepository
    {
        public Task<ProductDetail> GetProductByIdAsync(int id);
    }
}

Renderinglogik

Das deklarative Markup in einer Razor-Komponentendatei (.razor) wird in eine C#-Methode namens BuildRenderTree kompiliert. Beim Rendern einer Komponente wird von BuildRenderTree eine Datenstruktur ausgeführt und erstellt, die Elemente, Text und untergeordnete Komponenten der gerenderten Komponente beschreibt.

Die Renderinglogik kann eine Ausnahme auslösen. Ein Beispiel für dieses Szenario tritt auf, wenn @someObject.PropertyName ausgewertet wird, @someObject jedoch null ist. Bei Blazor-Apps, die über eine Leitung betrieben werden, ist ein von der Renderinglogik ausgelöster Ausnahmefehler für die Leitung der App schwerwiegend.

Um in einer Renderinglogik eine NullReferenceException zu vermeiden, prüfen Sie vor dem Zugriff auf die entsprechenden Member, ob ein null-Objekt vorhanden ist. Im folgenden Beispiel wird auf die person.Address-Eigenschaften nicht zugegriffen, wenn person.Addressnull ist:

@if (person.Address != null)
{
    <div>@person.Address.Line1</div>
    <div>@person.Address.Line2</div>
    <div>@person.Address.City</div>
    <div>@person.Address.Country</div>
}

Für den obigen Code wird angenommen, dass person nicht null ist. Häufig wird durch die Codestruktur sichergestellt, dass ein Objekt zu dem Zeitpunkt vorhanden ist, zu dem die Komponente gerendert wird. In diesen Fällen muss nicht geprüft werden, ob null in der Renderinglogik vorhanden ist. Im vorigen Beispiel kann garantiert werden, dass person vorhanden ist, da person beim Instanziieren der Komponente erstellt wird, wie das folgende Beispiel veranschaulicht:

@code {
    private Person person = new();

    ...
}

Ereignishandler

Clientseitiger Code löst Aufrufe von C#-Code aus, wenn Ereignishandler mit folgenden Elementen erstellt werden:

  • @onclick
  • @onchange
  • Mit anderen @on...-Attributen
  • @bind

Der Ereignishandlercode löst in diesen Szenarios möglicherweise einen Ausnahmefehler aus.

Für den Fall, dass durch die App Code aufgerufen wird, der aus externen Gründen einen Fehler verursachen könnte, müssen Ausnahmen mithilfe einer try-catch-Anweisung mit Fehlerbehandlung und Fehlerprotokollierung abgefangen werden.

Wenn ein Ereignishandler einen nicht behandelten Ausnahmefehler auslöst (wenn beispielsweise bei einer Datenbankabfrage ein Fehler auftritt), der vom Entwicklercode nicht abgefangen und behandelt wird, geschieht Folgendes:

  • Das Framework protokolliert den Ausnahmefehler.
  • Bei Blazor-Apps, die über eine Leitung betrieben werden, ist die Ausnahme für die Leitung der App schwerwiegend.

Beseitigung von Komponenten

Eine Komponente kann von der Benutzeroberfläche entfernt werden, weil der Benutzer beispielsweise zu einer anderen Seite navigiert ist. Wenn eine Komponente, die System.IDisposable implementiert, von der Benutzeroberfläche entfernt wird, ruft das Framework die Dispose-Methode der Komponente auf.

Wenn die Dispose-Methode der Komponente einen Ausnahmefehler in einer Blazor-App auslöst, die über eine Leitung betrieben wird, ist die Ausnahme für die Leitung der App schwerwiegend.

Wenn die Logik zum Verwerfen Ausnahmen auslösen kann, sollte die App die Ausnahmen mit einer try-catch-Anweisung mit Fehlerbehandlung und Protokollierung abfangen.

Weitere Informationen zur Beseitigung von Komponenten finden Sie unter Lebenszyklus von Razor-Komponenten in ASP.NET Core.

JavaScript-Interoperabilität

IJSRuntime wird vom Blazor-Framework registriert. Mit IJSRuntime.InvokeAsync kann .NET-Code die JavaScript-Runtime (JS) im Browser des Benutzers asynchron aufrufen.

Die folgenden Bedingungen gelten für die Fehlerbehandlung mit InvokeAsync:

  • Wenn ein Aufruf von InvokeAsync synchron fehlschlägt, tritt eine .NET-Ausnahme auf. Ein Aufruf von InvokeAsync kann beispielsweise fehlschlagen, wenn die bereitgestellten Argumente nicht serialisiert werden können. Die Ausnahme muss vom Entwicklercode abgefangen werden. Wenn eine Ausnahme in einer Blazor-App, die über eine Leitung betrieben wird, nicht vom App-Code in einem Ereignishandler oder in der Lebenszyklusmethode einer Komponente behandelt wird, ist die resultierende Ausnahme für die Leitung der App schwerwiegend.
  • Wenn ein Aufruf von InvokeAsync asynchron fehlschlägt, schlägt die .NET Task fehl. Ein Aufruf von InvokeAsync kann beispielsweise fehlschlagen, wenn der Code auf JS-Seite eine Ausnahme auslöst oder eine Promise zurückgibt, die als rejected abgeschlossen wird. Die Ausnahme muss vom Entwicklercode abgefangen werden. Wenn Sie den await-Operator verwenden, sollten Sie in Erwägung ziehen, den Methodenaufruf mithilfe einer try-catch-Anweisung mit Fehlerbehandlung und Fehlerprotokollierung zu umschließen. Andernfalls führt der fehlerhafte Code in einer Blazor-App, die über eine Leitung betrieben wird, zu einem Ausnahmefehler, der für die Leitung der App schwerwiegend ist.
  • Anrufe an InvokeAsync müssen innerhalb eines bestimmten Zeitraums abgeschlossen werden, andernfalls wird der Anruf unterbrochen. Der Standardzeitraum für die Zeitüberschreitung beträgt eine Minute. Mit dem Zeitlimit wird der Code vor dem Verlust der Netzwerkkonnektivität oder vor JS-Code geschützt, der keine Abschlussmeldung sendet. Wenn beim Aufruf ein Timeout auftritt, schlägt die resultierende System.Threading.Tasks mit einer OperationCanceledExceptionfehl. Die Ausnahme wird abgefangen und mit Protokollierung verarbeitet.

Ähnlich kann JS-Code Aufrufe von .NET-Methoden einleiten, die vom [JSInvokable]-Attribut angegeben werden. Wenn diese .NET-Methoden einen Ausnahmefehler auslösen:

  • In einer Blazor-App, die über einen Schaltkreis arbeitet, wird die Ausnahme nicht als fatal für den Schaltkreis der App behandelt.
  • Die JS-seitige Element Promise wird abgelehnt.

Sie können Fehlerbehandlungscode auf der .NET-Seite oder JS-Seite des Methodenaufrufs verwenden.

Weitere Informationen finden Sie in den folgenden Artikeln:

Voarabrendering

Razor-Komponenten können vorab gerendert werden, sodass das gerenderte HTML-Markup als Teil der ursprünglichen HTTP-Benutzeranforderung zurückgegeben wird.

In einer Blazor-App, die über eine Leitung betrieben wird, funktioniert das Vorabrendering folgendermaßen:

  • Erstellen einer neuen Leitung für alle vorab gerenderten Komponenten, die Teil derselben Seite sind
  • Generieren des ursprünglichen HTML-Codes
  • Behandeln der Leitung als disconnected, bis der Browser des Benutzers eine SignalR-Verbindung zurück zum selben Server einrichtet. Wenn die Verbindung hergestellt wird, wird die Interaktivität in der Leitung fortgesetzt und das HTML-Markup der Komponenten wird aktualisiert.

Bei vorab gerenderten clientseitigen Komponenten funktioniert das Vorabrendering folgendermaßen:

  • Generieren des anfänglichen HTML-Codes auf dem Server für alle vorab gerenderten Komponenten, die Teil einer Seite sind.
  • Gestalten der Komponente auf dem Client als interaktiv, nachdem der Browser den kompilierten Code der App und die .NET-Runtime (sofern noch nicht geladen) im Hintergrund geladen hat.

Wenn von einer Komponente während des Vorabrenderns ein Ausnahmefehler beispielswiese während einer Lebenszyklusmethode oder in der Renderinglogik ausgelöst wird, gilt Folgendes:

  • Bei Blazor-Apps, die über eine Leitung betrieben werden, ist die Ausnahme für die Leitung schwerwiegend. Bei vorab gerenderten clientseitigen Komponenten verhindert die Ausnahme, dass die Komponente gerendert wird.
  • Die Ausnahme wird vom ComponentTagHelper in der Aufrufliste weiter oben ausgelöst.

Wenn das Prerendering unter normalen Umständen fehlschlägt, ist es nicht sinnvoll, mit dem Erstellen und Rendern der Komponente fortzufahren, da eine funktionierende Komponente nicht gerendert werden kann.

Wenn während des Prerenderings auftretende Fehler toleriert werden sollen, muss sich die Fehlerbehandlungslogik in einer Komponente befinden, die möglicherweise Ausnahmen auslöst. Verwenden Sie try-catch-Anweisungen mit Fehlerbehandlung und Fehlerprotokollierung. Statt das ComponentTagHelper in einer try-catch-Anweisung zu umschließen, fügen Sie eine Fehlerbehandlungslogik in die vom ComponentTagHelper gerenderte Komponente ein.

Erweiterte Szenarien

Rekursives Rendering

Komponenten können rekursiv geschachtelt sein. Dies ist nützlich, um rekursive Datenstrukturen darzustellen. So kann eine TreeNode-Komponente beispielsweise weitere TreeNode-Komponenten für die einzelnen untergeordneten Elemente des Knotens rendern.

Vermeiden Sie beim rekursiven Rendern Codemuster, die zu einer Endlosschleife führen:

  • Vermeiden Sie rekursives Rendering bei einer Datenstruktur, die einen Zyklus enthält. Rendern Sie beispielsweise keinen Strukturknoten, dessen untergeordnete Knoten sich selbst enthalten.
  • Erstellen Sie keine Layoutkette, die einen Zyklus enthält. Erstellen Sie beispielsweise kein Layout, das sein eigenes Layout ist.
  • Lassen Sie nicht zu, dass ein Endbenutzer Rekursionsinvarianten (Regeln) durch eine böswillige Dateneingabe oder JavaScript-Interoperabilitätsaufrufe verletzt.

Endlosschleifen durch Rendering:

  • sorgt dafür, dass der Renderingprozess unendlich fortgesetzt wird.
  • entspricht dem Erstellen einer nicht abgeschlossenen Schleife.

In diesen Szenarien schlägt Blazor fehl, und es wird in der Regel Folgendes versucht:

  • Unbegrenzt so viel CPU-Zeit wie vom Betriebssystem zulässig zu nutzen.
  • Eine unbegrenzte Menge an Arbeitsspeicher zu nutzen. Das Belegen einer unbegrenzte Menge an Arbeitsspeicher entspricht einem Szenario, bei dem eine nicht beendete Schleife einer Auflistung bei jeder Iteration Einträge hinzufügt.

Wenn Sie Muster mit Endlosrekursionen vermeiden möchten, müssen Sie sicherstellen, dass der Renderingcode geeignete Beendigungsbedingungen enthält.

Benutzerdefinierte Renderstrukturlogik

Die meisten Razor-Komponenten sind als Razor-Komponentendateien (.razor) implementiert und werden vom Framework kompiliert, um die Logik zu erstellen, die einen RenderTreeBuilder zum Rendern der Ausgabe verwendet. Ein Entwickler kann eine RenderTreeBuilder-Logik allerdings mit prozeduralem C#-Code manuell implementieren. Weitere Informationen finden Sie unter Fortgeschrittene Szenarios zu ASP.NET Core Blazor (Renderstrukturerstellung).

Warnung

Die Verwendung einer manuellen Buildlogik für die Renderstruktur gilt als erweitertes und unsicheres Szenario, das für die allgemeine Komponentenentwicklung nicht empfohlen wird.

Beim Schreiben von RenderTreeBuilder-Code muss der Entwickler die Richtigkeit des Codes garantieren. Der Entwickler muss beispielsweise Folgendes sicherstellen:

  • Aufrufe von OpenElement und CloseElement sind ordnungsgemäß ausgeglichen.
  • Attribute werden nur an den richtigen Stellen hinzugefügt.

Eine fehlerhafte Strukturgenerator-Logik für das manuelle Rendering kann zu willkürlichem, undefiniertem Verhalten führen, einschließlich Abstürzen, einer nicht mehr reagierenden Anwendung bzw. einem Server und Sicherheitslücken.

Eine manuelle Buildlogik für die Renderstruktur ist genauso komplex und birgt dasselbe Maß an Gefahren wie das manuelle Schreiben von Assemblycode oder MSIL-Anweisungen (Microsoft Intermediate Language).

Zusätzliche Ressourcen

†Gilt für Back-End-Web-API-Apps in ASP.NET Core, die clientseitige Blazor-Apps für die Protokollierung verwenden.