Display unhandled client exceptions with Blazor

Unfortunately, the default error message for unhandled exceptions of Blazor doesn't provide any information of the exception. What I needed was to show these information to test user on a test system so it's easier to report errors.

An extension of the default blazor error message ("An unhandled error has occurred. Reload") which includes the exception message and stack trace would be ideal.

Solution

In index.html I added a new element "error-detail":

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

I also implemented a new logger provider to deal with unhandled exceptions. The UnhandledExceptionLogger is receiving a reference to the UnhandledExceptionProvider which contains an event, that is called whenever an unhandled exception occurs:

public class UnhandledExceptionProvider : ILoggerProvider { public event Action<LogLevel, Exception?>? Log; public UnhandledExceptionProvider() { } public ILogger CreateLogger(string categoryName) { return new UnhandledExceptionLogger(this); } public void Dispose() { } private class UnhandledExceptionLogger : ILogger { private readonly UnhandledExceptionProvider unhandledExceptionProvider; public UnhandledExceptionLogger(UnhandledExceptionProvider unhandledExceptionProvider) { this.unhandledExceptionProvider = unhandledExceptionProvider; } public bool IsEnabled(LogLevel logLevel) { return true; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { // Unhandled exceptions will call this method unhandledExceptionProvider.OnLog?.Invoke(logLevel, exception); } public IDisposable? BeginScope<TState>(TState state) where TState : notnull { return new EmptyDisposable(); } private class EmptyDisposable : IDisposable { public void Dispose() { } } } }

Now in Program.cs I can just use Javascript to set the error details in the DOM:

public static async Task Main(string[] args) { ... var unhandledExceptionProvider = new UnhandledExceptionProvider(); builder.Logging.AddProvider(unhandledExceptionProvider); WebAssemblyHost host = builder.Build(); unhandledExceptionProvider.Log += (LogLevel, exception) => { if (logLevel == LogLevel.Critical && exception != null) { var jsRuntime = host.Services.GetRequiredService<IJSRuntime>(); string stackTrace = exception.StackTrace != null ? Encoding.UTF8.GetString(Encoding.UTF32.GetBytes(exception.StackTrace)) : string.Empty; string errorDetail = exception.Message + "<br>" + HttpUtility.HtmlEncode(stackTrace).Replace(@"\", @"\\").Replace("\n", "<br>"); jsRuntime.InvokeVoidAsync("eval", $"document.getElementById('error-detail').innerHTML='{errorDetail}';"); } }; await host.RunAsync(); }

Of course you can also show different information. You could even add a button to show and hide the stack trace and so on. I hope someone will find this useful :)