Global Exception Handling in ASP.NET Core Blazor WebAssembly mit .NET 5

Ein bei Entwicklern vielleicht nicht immer so beliebtes, aber für den Endbenutzer enorm wichtiges Thema, ist das Abfangen und Behandeln von Fehlern. Selbst wenn eine Anwendung vor der Bereitstellung gründlich getestet wurde ist es immer möglich, dass der Benutzer auf (unerwartete) Fehler stößt. In diesem Artikel schauen wir uns anhand eines Beispiels an, wie wir Fehler in Blazor WebAssembly an zentraler Stelle abfangen und verarbeiten können.

In diesem Artikel:

Version Information

  • .NET SDK 5.0.104
  • ASP.NET Core Blazor WebAssembly: 5.0.4
  • MudBlazor: 5.0.4

Der gesamte Source Code zur Beispielanwendung findet sich in diesem GitHub Repository.

Exception Handling als Aufgabenstellung

Der richtige Umgang mit Fehlern ist für die Erstellung einer robusten Anwendung in Blazor WebAssembly von entscheidender Bedeutung. Fehlerbehandlungsroutinen bieten die Möglichkeit, dass wir benutzerfreundliche Informationen präsentieren und wichtige Daten für die Entwicklung sammeln können. Im Zeitalter von fortgeschrittenen Front-End-Web-Apps ist es wichtiger denn je, eine effektive, clientseitige Lösung für die Fehlerbehandlung zu haben.

Eine Anwendung, die Fehler nicht ordnungsgemäß behandelt, lässt ihre Benutzer verwirrt und frustriert zurück, wenn die App plötzlich ohne Erklärung unterbrochen wird. Der korrekte Umgang mit diesen Fehlern in einer Anwendung verbessert die Benutzererfahrung erheblich. Gesammelte Daten aus der Fehlerbehandlung können das Entwicklungsteam über wichtige, nach dem Testen aufgetretene Probleme informieren.

Treten in einer Blazor WebAssembly SPA unbehandelte Fehler auf, kann dies fatale Folgen haben für die weitere Nutzung der Anwendung. Denn der Benutzer kann nur fortfahren und die Webanwendung weiter nutzen, indem er die Seite neu lädt. Bei einem nicht behobenen Fehler, wird am unteren Rand der Seite eine Meldung angezeigt: An unhandled error has occurred.. Die Meldung fordert den Nutzer dann auf die Seite neu zu laden.

Würde das Framework versuchen, trotz des Fehlers weiterzumachen, könnte nicht gewährleistet werden, dass die Anwendung weiter korrekt funktioniert. Je nach Aufbau der Webanwendung kann es sogar zu Sicherheitsproblemen kommen. Um dies zu beheben, müssen wir für das Auftreten von unbehandelten Fehlern eine geeignete Fehlerbehebung einbauen.

Lassen Sie uns also schauen, wie wir dies mit Blazor WebAssembly realisieren können. Denn auch in .NET 5 ist ein Global Exception Handling nicht offiziell Teil des Blazor APIs.

Global Exception Handling im .NET-Code

Um nicht behandelte Fehler im .NET-Code abfangen zu können, gibt es derzeit noch keine allgemeine Lösung. Ein Weg globale Exceptions abzufangen, ist einen eigenen LoggingProvider zu implementieren. Dadurch haben wir die sobald ein Log geschrieben wird die Möglichkeit zu prüfen, um welche Art von Exception es sich handelt. Bei einer Exception die einen Reload der Anwendung benötigt, kann durch eine eigene Implementierung des ILogger-Interface darauf reagiert werden, wie im folgenden Code-Beispiel zu sehen:

				
					public class CriticalExceptionLoggingProvider : ILoggerProvider
{
    private CriticalExceptionLogger _logger;

    public CriticalExceptionLoggingProvider(NavigationManager navigationManager)
    {
        _logger = new CriticalExceptionLogger(navigationManager);
    }

    public void Dispose()
    {
        _logger = null;
    }

    public ILogger CreateLogger()
    {
        if (_logger != null)
        {
            return _logger;
        }

        return NullLogger.Instance;
    }
}

public class CriticalExceptionLogger : ILogger
{
    private readonly NavigationManager _navigationManager;

    public CriticalExceptionLogger(NavigationManager navigationManager)
    {
        _navigationManager = navigationManager ?? throw new ArgumentNullException(nameof(navigationManager));
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
        Func<TState, Exception, string> formatter)
    {
        // Log Exception in einem log-File
        if (logLevel == LogLevel.Critical)
        {
            _navigationManager.NavigateTo(_navigationManager.BaseUri, true);
        }
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel == LogLevel.Error;
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }
}
				
			

Im obigen Code-Beispiel wird ein eigener LoggingProvider implementiert. Dieser gibt für das ILogger-Interface eine Instanz des CriticalExceptionLogger zurück. Der CriticalExceptionLogger prüft den Status der geworfenen Exception. Handelt es sich hier um einen fatalen Fehler mit dem LogLevel Critical, kann die SPA voraussichtlich nicht mehr weiter genutzt werden. Daher müssen wir im nächsten Schritt einen Reload der Anwendung erzwingen, um dem Nutzer die Möglichkeit zu geben, die Anwendung neuerlich nutzen zu können.

Mit Hilfe des NavigationManager, welchen wir per Dependency Injection dem CriticalExceptionLogger verfügbar machen, kann die aktuelle Seite der Anwendung neu geladen werden. Da es sich hier um Sample-Code handelt, wird die Exception vor dem Reload der Anwendung nicht weiterverarbeitet. Jedoch wäre es sinnvoll die Informationen aller Exception abzuspeichern oder an ein Web API zu senden, um im Nachgang den Fehler auslesen und beheben zu können.

Um den LoggingProvider in unserer Anwendung nutzen zu können, muss dieser in der ServiceCollection registriert werden.

				
					builder.Services.AddSingleton<ILoggerProvider, CriticalExceptionLoggingProvider>(services =>
{
    var navigationManager = services.GetService<NavigationManager>();
    return new CriticalExceptionLoggingProvider(navigationManager);
});
				
			

Doch nicht nur im .NET Core-Code können unbehandelte Fehler geworfen werden. Auch im JavaScript-Code können diese auftreten und müssen adressiert werden. Im nächsten Abschnitt schauen wir uns an, wie Sie diese Fehler abfangen und im .NET-Code weiter verarbeiten können.

Global Exception Handling im JavaScript-Code

Aufruf einer .NET-Methode mit Hilfe von JS Interop

Um in unserer Anwendung ein globales Exception Handling einbauen zu können, müssen wir die Exceptions an einer übergeordneten Stelle abfangen. Da sich eine Blazor WebAssembly SPA immer ins HTML DOM einbettet, müssen wir dies in .NET 5 (und auch in den Versionen davor) über JS Interop realisieren. Hierzu gehen wir im ersten Schritt in die index.html unserer Blazor-WebAssembly-Anwendung und fügen folgenden Code in einem neuen <script>-Tag hinzu.

				
					
				
			

Im obigen Code werden in zwei Fällen JavaScript Errors abgefangen und an den C#-Client-Code weitergeleitet. Eine davon ist über die console.error-Funktion. Hier wird, sobald ein Error in die Console geschrieben wird, die Nachricht abgefangen und mit der JavaScript-Methode reportError an den Blazor-Code weitergegeben.

Die zweite Implementierung registriert den EventListener für das Browser Event unhandledrejection. Hier werden alle Exceptions abgefangen, welche nicht bereits abgefangen und behandelt wurden. Wenn ein Fehler auftritt, wird dieser in unserem Code auch weiter an die JavaScript-Methode reportError gegeben.

Abhängig von der Art des Fehlers kann die Eigenschaft reason unterschiedlich typisiert sein. Da alle Typen jedoch von Error ableiten, gibt es eine Eigenschaft, die immer existiert: message. Mit reason.message erhält man unabhängig des konkreten Fehlers immer die Error-Nachricht. Diese wird dann an die Methode reportError weitergegeben werden.

Möchte man das ganze Objekt im .NET-Code entgegennehmen, ist zu beachten, dass auch dort jeder Typ entgegengenommen und verarbeitet werden kann. Dies beinhaltet mindestens zwei Herausforderungen die umgesetzt werden müssen:

  • Es muss geprüft werden, um welchen Typ es sich bei dem übergebenen Objekt handelt.
  • Und es muss geprüft werden, um welche Art von Fehler es sich handelt.

In der Funktion repeatError wird geprüft, ob die DotNet-API vorhanden ist (was bei Blazor WebAssembly der Fall ist). Ist dem so, wird mit der Methode invokeMethodAsync die Exception an den Blazor WebAssembly .NET Code weitergeleitet und dort die Methode HandleJSException aufgerufen.

Beim Aufruf von DotNet.invokeMethodAsync ist es wichtig als ersten Parameter den Namespace zu übergeben, in welchem die Methode implementiert wurde. In unserem Beispiel ist dies Blazor.GlobalExceptionHandling. Der zweite Parameter gibt den Namen der Methode an, welche im Blazor .NET Code als JSInvokable implementiert wird. Dort werden wir die Exceptions dann final behandeln.

JavaScript Exception im .NET Code behandeln

Im zweiten Schritt müssen wir die Methode HandleJSException implementieren, um die Exceptions im C#-Code abfangen zu können. Hierzu erstellen wir eine statische Methode und fügen das Attribut JSInvokable hinzu. Als Parameter wird der Name übergeben, welchen wir im letzten Schritt im JavaScript-Methoden-Aufruf invokeMethodAsync definiert haben.

				
					public partial class MainLayout
{
  [JSInvokable("HandleJSException")]
  public static async Task HandleJSException(JavaScriptException error)
  {
    //Handle errors
  }
}
				
			

Am Code-Beispiel sehen wir, dass die Exception als JavaScriptException-Parameter mit der Methode übergeben wird. Wir haben hier also keine echte .NET Exception verfügbar, sondern den Fehler aus der JavaScript-Welt. So können wir nun den Fehler behandeln, um die Stabilität und Usability unserer Anwendung zu verbessern.

Das war’s auch schon!

So können mit zwei kleinen Anpassungen globale Exceptions in Blazor WebAssembly abgefangen und verarbeitet werden.

Fazit

In diesem Artikel haben wir zwei Varianten kennengelernt, mit welcher wir Exceptions in einer Blazor WebAssembly SPA abfangen und behandeln können. Im ersten Abschnitt konnten wir im .NET-Code mit Hilfe eines eigenen LoggingProvider, Exceptions abfangen und behandeln. Hier ist es wichtig darauf zu achten, dass bei kritischen Exceptions eine hohe Wahrscheinlchkeit besteht, dass die SPA nicht mehr richtig läuft und neu geladen werden muss damit der Nutzer die Seite weiter nutzen kann. Im zweiten Abschnitt des Artikels haben wir mit cleverem Einsatz von JS Interop nicht behandelten Exceptions an den .NET-Code weitergegeben. Doch wie wir auch gesehen haben ist das Weitergeben der Exceptions nicht die Herausforderung, sondern das Verarbeiten der Exception. Dies kann je nach Implementierung im .NET Code sehr komplex werden, da das JavaScript-Objekt, je nach Exception, einen anderen Typ besitzt kann. Hier ist es wichtig, die Exception so zu verarbeiten, dass der Client weiter funktioniert und der Fehler so dokumentiert und kommuniziert wird, dass dieser schnell behoben werden kann.

Beide Varianten des Exception Handlings können in .NET 5 und natürlich auch in früheren .NET Core Version eingesetzt werden. Für .NET 6 wurde bereits ein Designvorschlag für die Umsetzung von Global Exception Handling in Blazor WebAssembly erstellt.

Der gesamte Source Code zu diesem Artikel befindet sich im zugehörigen GitHub Repository.

Mehr Artikel zu Blazor, WebAssembly
Kostenloser
Newsletter

Aktuelle Artikel, Screencasts, Webinare und Interviews unserer Experten für Sie

Verpassen Sie keine Inhalte zu Angular, .NET Core, Blazor, Azure und Kubernetes und melden Sie sich zu unserem kostenlosen monatlichen Dev-Newsletter an.

Newsletter Anmeldung
Diese Artikel könnten Sie interessieren
Blazor
favicon

gRPC Code-First mit ASP.NET Core 7 und Blazor WebAssembly

Wie in allen anderen browserbasierten Single-Page-Application (SPA) Frameworks, ist Blazor WebAssembly JSON-over-HTTP (über Web- oder REST-APIs) die bei weitem häufigste Methode, um Daten auszutauschen und serverseitige Vorgänge auszulösen. Der Client sendet eine HTTP-Anfrage mit JSON-Daten an eine URL, mitunter über unterschiedliche HTTP-Verben. Anschließend führt der Server eine Operation aus und antwortet mit einem HTTP-Statuscode und den resultierenden JSON-Daten. Warum sollte man das ändern? Nun, es gibt Gründe - vor allem wenn man in einem geschlossenen System ist und .NET sowohl im Frontend als auch im Backend einsetzt.
30.03.2023
Blazor
favicon

Blazor WebAssembly in .NET 7: UI-Performance-Optimierung auf Komponentenebene

Stockende UI, keine Reaktion nach dem Klick auf einen Button oder einer Eingabe in einem Feld - dies sind nur wenige Beispiele alltäglicher Probleme, die der Nutzung von Client-Anwendungen im Allgemeinen, und bei Webanwendungen im Speziellen, immer wieder auftreten können. In diesem Artikel schauen wir uns an, wie wir komponentenbasierte UIs in Blazor WebAssembly optimieren können, um dadurch eine für die Benutzer zufriedenstellende Geschwindigkeit und ein flüssiges UI zu bekommen.
29.03.2023
Blazor
sg

Understanding and Controlling the Blazor WebAssembly Startup Process

There are a lot of things going on in the background, when a Blazor WebAssembly application is being started. In some cases you might want to take a bit more control over that process. One example might be the wish to display a loading screen for applications that take some time for initial preparation, or when users are on a slow internet connection. However, in order to control something, we need to understand what is happening first. This article takes you down the rabbit hole of how a Blazor WASM application starts up.
07.03.2023
.NET
cl-neu

Adding Superpowers to your Blazor WebAssembly App with Project Fugu APIs

Blazor WebAssembly is a powerful framework for building web applications that run on the client-side. With Project Fugu APIs, you can extend the capabilities of these apps to access new device features and provide an enhanced user experience. In this article, learn about the benefits of using Project Fugu APIs, the wrapper packages that are available for Blazor WebAssembly, and how to use them in your application.

Whether you're a seasoned Blazor developer or just getting started, this article will help you add superpowers to your Blazor WebAssembly app.
28.02.2023
Blazor
cl-neu

Blazor WebAssembly in Practice: Maturity, Success Factors, Showstoppers

ASP.NET Core Blazor is Microsoft's framework for implementing web-based applications, aimed at developers with knowledge of .NET and C#. It exists alongside other frameworks such as ASP.NET Core MVC. About two and a half years after the release of Blazor WebAssembly and based on our experiences from many customer projects at Thinktecture, we want to have a close look at the following questions: What is the current state of the framework? How can you successfully use Blazor? And where does it have limitations?
24.11.2022
Blazor
favicon

Blazor WebAssembly: Debugging gRPC-Web with Custom Chrome Developer Tools

If you are working with Blazor, gRPC is a big issue for transferring data from APIs to clients. One issue of developing with gRPC-Web is debugging the transmitted data because the data is in an efficient binary message format. In this article, I will show you how to solve this problem with the help of my NuGet.
17.11.2022