Fehlerbehandlung in ASP.NET Core Blazor-Apps

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.

Passen Sie in einer Blazor Server-App die Darstellung in der Pages/_Host.cshtml-Datei an:

Passen Sie in einer Blazor Server-App die Darstellung in der Pages/_Layout.cshtml-Datei an:

Passen Sie in einer Blazor Server-App die Darstellung in der Pages/_Host.cshtml-Datei an:

<div id="blazor-error-ui">
    <environment include="Staging,Production">
        An error has occurred. This application may no longer respond until reloaded.
    </environment>
    <environment include="Development">
        An unhandled exception has occurred. See browser dev tools for details.
    </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">
    An unhandled error has occurred.
    <a href="" class="reload">Reload</a>
    <a class="dismiss">🗙</a>
</div>

Das blazor-error-ui-Element ist normalerweise ausgeblendet, weil der display: none-Stil der blazor-error-ui-CSS-Klasse im Stylesheet der Website (wwwroot/css/site.css für Blazor Server und wwwroot/css/app.css für Blazor WebAssembly) vorhanden ist. Wenn ein Fehler auftritt, wird display: block vom Framework auf das Element angewendet.

Detaillierte Leitungsfehler

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 Entwicklungsumgebung 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 Servern für Entwicklungs-/Stagingumgebungen 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.

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.

Blazor Server: nicht behandelte Ausnahmen

Dieser Abschnitt gilt für Blazor Server-Apps.

Blazor Server ist ein zustandsbehaftetes Framework. Während Benutzer mit einer App interagieren, besteht 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

Informationen zur globalen Ausnahmebehandlung finden Sie in den folgenden Abschnitten:

Fehlergrenzen

Blazor ist ein clientseitiges Framework für eine Single-Page-Webanwendung (Single-Page Application, SPA). Der Browser fungiert als der App-Host und folglich als Verarbeitungspipeline für einzelne Razor-Komponenten, die für die Navigation und statische Ressourcen auf URI-Anforderungen basieren. Im Gegensatz zu ASP.NET Core-Apps, die mit einer Middlewareverarbeitungspipeline auf dem Server ausgeführt werden, gibt es keine Middlewarepipeline, die Anforderungen für Razor-Komponenten verarbeitet und für die globale Fehlerbehandlung verwendet werden könnte. Eine App kann jedoch eine Fehlerverarbeitungskomponente als kaskadierenden Wert verwenden, um Fehler zentral zu verarbeiten.

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 Fehleroberfläche, wenn eine nicht behandelte Ausnahme ausgelöst wird.

Zum Definieren einer Fehlergrenze verwenden Sie die ErrorBoundary-Komponente, vorhandenen Inhalt zu umschließen. Beispielsweise kann eine Fehlergrenze um den Textinhalt des Hauptlayouts der App hinzugefügt werden.

Shared/MainLayout.razor:

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

Die App funktioniert weiterhin normal, die Fehlergrenze behandelt aber nicht behandelte Ausnahmen.

Betrachten Sie das folgende Beispiel, in dem die Counter-Komponente eine Ausnahme auslöst, wenn die Anzahl größer fünf wird.

In Pages/Counter.razor:

private void IncrementCount()
{
    currentCount++;

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

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

  • Die ausgelöste Ausnahme wird von der Fehlergrenze verarbeitet.
  • Die Fehlerbenutzeroberfläche wird gerendert (An error has occurred.).

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 die CSS im Stylesheet der App im wwwroot-Ordner definiert, sodass Sie die Fehlerbenutzeroberfläche beliebig anpassen können.

Sie können den Standardfehlerinhalt auch ändern, indem Sie die ErrorContent-Eigenschaft festlegen:

<ErrorBoundary>
    <ChildContent>
        @Body
    </ChildContent>
    <ErrorContent>
        <p class="errorUI">Nothing to see here right now. Sorry!</p>
    </ErrorContent>
</ErrorBoundary>

Da die Fehlergrenze in den vorherigen Beispielen im Layout definiert ist, wird die Fehlerbenutzeroberfläche unabhängig davon angezeigt, zu welcher Seite der Benutzer navigiert ist. 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:

...

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

...

@code {
    private ErrorBoundary? errorBoundary;

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

Alternative globale Ausnahmebehandlung

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

Shared/Error.razor:

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

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

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

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {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 Error-Komponente einen Wrapper für die Router-Komponente. Dies erlaubt der Error-Komponente eine Kaskadierung an beliebige Komponenten der App, wobei die Error-Komponente als CascadingParameter empfangen wird.

App.razor:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

So verarbeiten Sie Fehler in einer-Komponente:

  • Legen Sie die Error-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 Error-Eigenschaft hinzu:

    [CascadingParameter]
    public Error? Error { get; set; }
    
  • Rufen Sie eine Fehlerverarbeitungsmethode mit einem entsprechenden Ausnahmetyp in einem beliebigen catch-Block auf. Die Error-Komponente aus dem Beispiel verfügt nur über eine einzige ProcessError-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. Im folgenden Beispiel für eine Counter-Komponente wird eine Ausnahme ausgelöst und abgefangen, wenn die Anzahl größer als fünf ist:

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

Mithilfe der vorherigen Error-Komponente mit den vorherigen Änderungen, die an einer Counter-Komponente vorgenommen wurden, gibt die Konsole mit den Entwicklertools des Browsers den abgefangenen, protokollierten Fehler an:

fail: BlazorSample.Shared.Error[0]
Error:ProcessError - Type: System.InvalidOperationException Message: Current count is over five!

Wenn die ProcessError-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 ProcessErrors-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 Server-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 Blazor Server-App auf nicht behandelte Ausnahmen reagiert.

Blazor ist ein clientseitiges Framework für eine Single-Page-Webanwendung (Single-Page Application, SPA). Der Browser fungiert als der App-Host und folglich als Verarbeitungspipeline für einzelne Razor-Komponenten, die für die Navigation und statische Ressourcen auf URI-Anforderungen basieren. Im Gegensatz zu ASP.NET Core-Apps, die mit einer Middlewareverarbeitungspipeline auf dem Server ausgeführt werden, gibt es keine Middlewarepipeline, die Anforderungen für Razor-Komponenten verarbeitet und für die globale Fehlerbehandlung verwendet werden könnte. Eine App kann jedoch eine Fehlerverarbeitungskomponente als kaskadierenden Wert verwenden, um Fehler zentral zu verarbeiten.

Die folgende Error-Komponente wird von sich selbst als CascadingValue an die untergeordneten Komponenten übergeben. 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.

Shared/Error.razor:

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

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

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

    public void ProcessError(Exception ex)
    {
        Logger.LogError("Error:ProcessError - Type: {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 Error-Komponente einen Wrapper für die Router-Komponente. Dies erlaubt der Error-Komponente eine Kaskadierung an beliebige Komponenten der App, wobei die Error-Komponente als CascadingParameter empfangen wird.

App.razor:

<Error>
    <Router ...>
        ...
    </Router>
</Error>

So verarbeiten Sie Fehler in einer-Komponente:

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

    [CascadingParameter]
    public Error Error { get; set; }
    
  • Rufen Sie eine Fehlerverarbeitungsmethode mit einem entsprechenden Ausnahmetyp in einem beliebigen catch-Block auf. Die Error-Komponente aus dem Beispiel verfügt nur über eine einzige ProcessError-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)
    {
        Error.ProcessError(ex);
    }
    

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

Fehler: BlazorSample.Shared.Error[0] Error:ProcessError – Typ: System.NullReferenceException Meldung: Objektverweis nicht auf eine Instanz eines Objekts festgelegt.

Wenn die ProcessError-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 ProcessErrors-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 Server-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 Blazor Server-App auf nicht behandelte Ausnahmen 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 für die Protokollierung einen Speicherort auf dem Server (oder der Back-End-Web-API für Blazor WebAssembly-Apps) mit einem Anbieter verwenden, der die Protokollgröße und 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 von Blazor WebAssembly-Apps und 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 Blazor WebAssembly-App das Application Insights JavaScript SDK mit JS Interop verwenden, um Fehler aus einer clientseitigen App direkt in Application Insights zu protokollieren.

Während der Entwicklung in einer Blazor Server-App sendet die App zur Unterstützung beim Debuggen 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:

‡Gilt für Blazor Server-Apps und andere serverseitige ASP.NET Core-Apps, die als Web-API-Back-End-Apps für Blazor dienen. Blazor WebAssembly-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 von Blazor WebAssembly-Apps und native Unterstützung desBlazor -Frameworks für Google Analytics werden möglicherweise in kü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 Blazor WebAssembly-App das Application Insights JavaScript SDK mit JS Interop 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 es sich bei der App um eine Blazor Server-App handelt, tritt bei der Leitung ein Fehler auf. 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 Blazor Server-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}"
@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;
            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;
            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;
            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;
            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 Server-Apps ist ein von einer 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.
  • In einer Blazor Server-App 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 Server-App auslöst, ist die Ausnahme für die Leitung der App schwerwiegend.

Wenn eine Beseitigungslogik Ausnahmen auslösen kann, muss die App die Ausnahmen mithilfe einer try-catch-Anweisung mit Fehlerbehandlung und Fehlerprotokollierung 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 Server-App nicht von 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. Tun Sie dies in einer Blazor Server-App nicht, führt der fehlgeschlagene Code zu einem Ausnahmefehler, der für die Leitung der App schwerwiegend ist.
  • Ein Aufruf von InvokeAsync muss standardmäßig innerhalb eines bestimmten Zeitraums abgeschlossen werden, da ansonsten für den Aufruf ein Timeout auftritt. Die Standardwert für das Zeitlimit 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 Server-App wird die Ausnahme für die Leitung der App nicht als schwerwiegend 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 mit dem Komponententaghilfsprogramm vorab gerendert werden, sodass das gerenderte HTML-Markup als Teil der ursprünglichen HTTP-Anforderung des Benutzers zurückgegeben wird.

In Blazor Server funktioniert das Prerendering wie folgt:

  • 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 gerendertem Blazor WebAssembly funktioniert das Prerendering wie folgt:

  • 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:

  • In Blazor Sever-Apps ist Ausnahme für die Leitung schwerwiegend. In vorab gerenderten Blazor WebAssembly-Apps verhindert die Ausnahme das Rendern der Komponente.
  • 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 der Blazor WebAssembly-Thread oder die Blazor Server-Leitung fehl. Normalerweise wird 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 Buildlogik für die Renderstruktur kann ein willkürliches undefiniertes Verhalten wie Abstürze, ein Aufhängen von App (Blazor WebAssembly) oder Server (Blazor Server) und Sicherheitsrisiken verursachen.

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 WebAssembly-Apps für die Protokollierung verwenden.