🟨 Slightly different to Blazor WebAssembly
Error handling is a critical aspect of building robust and reliable Blazor applications. No matter how carefully you plan and code your application, errors and exceptions are inevitable. However, with the right strategies and tools, you can minimize the impact of errors and quickly diagnose and resolve issues when they arise. In this tutorial:
You can download the example code used in this topic on GitHub.
Handling an error involves 2 critical steps:
Errors can be caught in five different scopes:
HttpClient
: This scope catches any unsuccessful request with a status code other than 200. It's important to note that this doesn't necessarily mean there is an error.Blazor is a Single Page Application (SPA) framework. In the event that the first request fails, the error will be caught by the scope responsible for handling errors on the first request. In general, it's recommended to avoid accessing HttpContext
in Blazor. However, if Blazor fails to initiate, accessing HttpContext
can provide additional information to investigate the problem.
If you are using the Blazor Server App template instead of the Blazor Server App Empty template, the default scope for catching first request errors is Error.cshtml, which has the path /Error. If you need to customize the first request error page, you can follow these steps:
app.UseExceptionHandler("/BlazorSchoolError");
@page @model BlazorSchoolError @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <title>Error</title> <link href="~/css/site.css" rel="stylesheet" asp-append-version="true" /> </head> <body> <div> <div> An error has occurred. Press "Submit" to report the error. Open the console to see the logged information. <form method="post"> <button type="submit">Submit</button> </form> </div> </div> </body> </html>
@model
directive. For example:[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [IgnoreAntiforgeryToken] public class BlazorSchoolError : PageModel { public string? RequestId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); private readonly ILogger<BlazorSchoolError> _logger; public BlazorSchoolError(ILogger<BlazorSchoolError> logger) { _logger = logger; } public void OnGet() => RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; [SuppressMessage("Reliability", "CA2017:Parameter count mismatch", Justification = "<Pending>")] public void OnPost() { var exceptionHandler = HttpContext.Features.Get<IExceptionHandlerFeature>(); if (exceptionHandler is not null) { _logger.LogCritical("You can log the error with the detailed message in the exceptionHandler {exceptionHandler.Error.Message}"); _logger.LogCritical("You can log the error with the stack trace in the exceptionHandler {exceptionHandler.Error.StackTrace}"); } } }
When an error hasn't been caught by any other scopes, this is the last fallback to catch it. To make changes, open your Pages/_Host.cshtml file and locate the following element:
<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>
You can customize the content of this element to show a specific message whenever an error occurs on your website.
In Blazor web development, layouts are essential components defined by @inherits LayoutComponentBase
. The default layout, MainLayout.razor, contains the @Body
element, which renders child components. By wrapping @Body
with the ErrorBoundary
component, you can catch and handle all errors that may be thrown by the layout component.
<ErrorBoundary> @Body </ErrorBoundary>
Visit Website Layout to learn more about this crucial aspect of web development.
To catch errors that occur within a component, you can use an error handler component. For instance, you can wrap the component that may throw an error with an ErrorBoundary
component. Suppose you have a TriggerError
component defined as follows:
@inject HttpClient HttpClient <button type="button" @onclick='TriggerHttpClientErrorAsync'>Trigger HttpClient error</button> <button type="button" @onclick="TriggerClientSideError">Trigger client side error</button> @code { public async Task TriggerHttpClientErrorAsync() => await HttpClient.GetAsync("https://blazorschool.com"); public void TriggerClientSideError() => throw new Exception("Blazor School"); }
To use an ErrorBoundary
component to handle errors thrown by the TriggerError
component, you can wrap the TriggerError
component inside an ErrorBoundary
component in another component, as shown below:
MyComponent.razor
<ErrorBoundary> <TriggerError/> </ErrorBoundary>
HttpClient
wrapper techniqueIf you're building an application that might an API, it's essential to handle errors gracefully. One way to achieve this is by using the HttpClient
interfere technique. T This technique involves intercepting HttpClient
requests to catch any exceptions thrown by the API and log them appropriately. Visit API Interaction tutorial for more information.
The following code sample demonstrates the HttpClient
wrapper technique, but keep in mind that in a real project, it's best to avoid using this technique. In this example, we have two classes:
ExceptionRecorderService
: Responsible for keeping track of any exceptions that are thrown.BlazorSchoolHttpClientWrapper
: This class is a custom HttpClient
wrapper that catches exceptions and logs them using the ExceptionRecorderService
.public class ExceptionRecorderService { public ObservableCollection<Exception> Exceptions { get; set; } = new(); }
public class BlazorSchoolHttpClientWrapper { private readonly HttpClient _httpClient; private readonly ExceptionRecorderService _exceptionRecorderService; public BlazorSchoolHttpClientWrapper(HttpClient httpClient, ExceptionRecorderService exceptionRecorderService) { _httpClient = httpClient; _exceptionRecorderService = exceptionRecorderService; } public async Task<HttpResponseMessage> GetAsync(string? requestUri) { var response = new HttpResponseMessage(); try { response = await _httpClient.GetAsync(requestUri); } catch (Exception ex) { _exceptionRecorderService.Exceptions.Add(ex); } return response; } }
To use this technique in your application, register both classes in your Program.cs file using the AddScoped
and AddHttpClient
methods.
builder.Services.AddScoped<ExceptionRecorderService>(); builder.Services.AddHttpClient<BlazorSchoolHttpClientWrapper>();
Once you've registered the classes, you can inject the BlazorSchoolHttpClientWrapper
and ExceptionRecorderService
into any component and use them to catch and handle API errors.
@inject ExceptionRecorderService ExceptionRecorderService @inject BlazorSchoolHttpClientWrapper BlazorSchoolHttpClientWrapper @implements IDisposable <h3>HandlingHttpErrorsByHttpClientWrapper</h3> <button @onclick="TriggerHttpErrorAsync">Trigger HTTP Error</button> @foreach (var exception in ExceptionRecorderService.Exceptions) { <div>@exception.Message</div> } @code { protected override void OnInitialized() { ExceptionRecorderService.Exceptions.CollectionChanged += RefreshUI; } public async Task TriggerHttpErrorAsync() { await BlazorSchoolHttpClientWrapper.GetAsync("https://unknown-wesite-2234123.com"); } public void RefreshUI(object? sender, NotifyCollectionChangedEventArgs eventArgs) { InvokeAsync(StateHasChanged); } public void Dispose() { ExceptionRecorderService.Exceptions.CollectionChanged -= RefreshUI; } }
In the example component provided, we inject the BlazorSchoolHttpClientWrapper
and ExceptionRecorderService
using the @inject
directive. We then use the BlazorSchoolHttpClientWrapper
to make an API call that triggers an error. The ExceptionRecorderService
catches the error and logs it. Finally, we use the logged error to display an error message to the user.
Blazor comes with an error handling pipeline that facilitates the handling of errors in different scopes based on a predefined sequence. When an unhandled error occurs, the error handling pipeline delegates it to the next scope in sequence.
The first error handler that handles the error is the one in the component scope. If there is no error handler in the component scope, the error is delegated to the layout scope. If there is still no error handler, the error is handled by the global error handler.
If the error is handled by any of the error handlers, it is not delegated to the next scope. However, if it remains unhandled, it continues to delegate to the next scope until it reaches the global error handler.
It's important to note that HTTP request errors are handled in a separate scope. You have the option to handle these errors or choose not to handle them. Unlike other errors, an HttpClient
error will not halt your website.
If the first request error occurs, your website will become unresponsive or inaccessible until the error is resolved.
To illustrate the error scope delegation, refer to the following image:
When it comes to handling errors in the second step, there are a couple of common actions you can take. In this section, we'll introduce you to two of them:
Displaying an error message to the user is a common action taken to handle errors in software. When an error occurs, displaying an error message can provide users with information about the error that occurred and how to resolve it. This can help users understand what went wrong and how to fix it, which can improve the user experience and reduce frustration.
The ErrorContent
element is a child element of the ErrorBoundary
component in Blazor, and it is used to define the content that will be displayed when an error occurs from the child components within the ErrorBoundary
scope.
The ErrorContent
element accepts a Context
parameter, which specifies the name of the variable that will hold the error object that caused the error. You can use this variable to access properties of the error object, such as the error message, and display it to the user.
For example, if you define an ErrorContent
element with the Context
parameter set to ex
, you can access the error message using the @ex.Message
syntax.
Here's an example of how to use the ErrorContent
element:
<ErrorBoundary> <ChildContent> <TriggerError/> </ChildContent> <ErrorContent Context="ex"> An error occurred: @ex.Message ErrorContent> </ErrorBoundary>
In some cases, it may be possible to resume the operation that was interrupted by the error. For example, if an error occurred during a payment transaction, the user may be able to retry the transaction or use an alternative payment method. Resuming the operation can help to minimize the impact of the error on the user experience and ensure that the user can complete the task they were attempting. You can resume the operation by using the ErrorBoundary
component and calling its Recover method as follows:
<ErrorBoundary @ref="MyErrorBoundary"> <ChildContent> <TriggerError/> </ChildContent> <ErrorContent> An error occurred. <button @onclick="_ => MyErrorBoundary?.Recover()">Continue</button> </ErrorContent> </ErrorBoundary> @code { public ErrorBoundary? MyErrorBoundary { get; set; } }
An error handler in Blazor is a component that extends either the ErrorBoundary
or ErrorBoundaryBase
component to handle errors within a specified scope. For instance, if you place an error handler at the component level, it will handle any errors that occur within that component. The ErrorBoundary
is the default error handler component, but you can also create custom error handlers.
In web development, having a custom error handler is crucial for several reasons. While the default error handler can handle most issues, there are instances where a more personalized approach is necessary.
For example, if your website throws more than 100 exceptions, it will be considered critical and fallback to the global error handler. Additionally, you may not have access to the current exception beyond basic details, limiting your ability to respond effectively. By creating a custom error handler, you gain access to the current exception, allowing you to take specific actions such as sending the exception back to your logging API or implementing other solutions.
To create a customized error handler, you can extend either the ErrorBoundary
or ErrorBoundaryBase
component. However, by extending the ErrorBoundaryBase
component, you will have greater flexibility in managing error situations.
To use the ErrorBoundaryBase
component effectively, it's important to understand its key features and how they work. Here are the main things to keep in mind:
ChildContent
: This is the component that may potentially throw an error. It's important to note that the error must be thrown within ChildContent
in order to be caught by the ErrorBoundaryBase
component. Refer to the documentation for common mistakes to avoid.ErrorContent
: This is a RenderFragment
that will be displayed if an error occurs within ChildContent
. It can be customized to provide a better user experience when errors occur.MaximumErrorCount
: This property sets a maximum number of errors that can occur before an error is treated as critical and delegated to the global error handler in Blazor. The default value for MaximumErrorCount
in ErrorBoundaryBase
is 100.Recover
: This is a method that allows you to reset the current error count and continue using the website without refreshing the page.OnErrorAsync
: This is an abstract method that you must override in your custom ErrorBoundaryBase
component. It will be called whenever an error is raised within ChildContent
, giving you the opportunity to handle the error in a way that makes sense for your application.To illustrate this, let's create a sample custom error handler that record a list of exceptions.
ErrorBoundaryBase
component, you should start by creating a new class that extends the ErrorBoundaryBase
class. For example:public class BlazorSchoolErrorBoundary : ErrorBoundaryBase { }
OnErrorAsync
method to record any exceptions that are thrown by the child components. This is where you can add custom logic to handle errors in a way that makes sense for your application. For example:public class BlazorSchoolErrorBoundary : ErrorBoundaryBase { ... public ObservableCollection<Exception> BlazorSchoolExceptions = new(); protected override Task OnErrorAsync(Exception exception) { BlazorSchoolExceptions.Add(exception); return Task.CompletedTask; } }
BuildRenderTree
method to display a default custom UI when an error occurs. This is where you can provide a method for the user to resolve the error, if applicable. For example:public class BlazorSchoolErrorBoundary : ErrorBoundaryBase { ... protected void RecoverAndClearErrors() { Recover(); BlazorSchoolExceptions.Clear(); } protected override void BuildRenderTree(RenderTreeBuilder builder) { if (CurrentException is null) { builder.AddContent(0, ChildContent); } else { if (ErrorContent is not null) { builder.AddContent(1, ErrorContent(CurrentException)); } else { builder.OpenElement(2, "div"); builder.AddContent(3, "Blazor School Custom Error Boundary."); builder.AddContent(4, __innerBuilder => { __innerBuilder.OpenElement(5, "button"); __innerBuilder.AddAttribute(6, "type", "button"); __innerBuilder.AddAttribute(7, "onclick", RecoverAndClearErrors); __innerBuilder.AddContent(8, "Continue"); __innerBuilder.CloseElement(); }); builder.CloseElement(); } } } }
Once you have created your custom error handler, you can use it just like the ErrorHandler
component. For instance:
<BlazorSchoolErrorBoundary> <ChildContent> <TriggerError /> </ChildContent> <ErrorContent> <TriggerError /> <div>Hello Blazor School!</div> </ErrorContent> </BlazorSchoolErrorBoundary>
It's common for enterprise websites to include an error reporting page, where users can report any issues they encounter to the developers. By utilizing a custom error handler, you can access the CurrentException
property, which contains information on the error that occurred. You can then send this object to the error reporting page, allowing you to gather valuable data from the user regarding the issue they faced.
When handling errors, it's important to avoid common mistakes that can lead to unexpected behavior. In this section, we'll discuss one such mistake.
According to data from the Blazor School Discord Community, a common error handling mistake is throwing exceptions outside of the error handler's scope and expecting the error handler to handle the exception. Consider the following code in the TriggerErrorOutsideScope
component:
@inject HttpClient HttpClient <ErrorBoundary> <ChildContent> <button type="button" @onclick='TriggerHttpClientErrorAsync'>Trigger HttpClient error</button> <button type="button" @onclick="TriggerClientSideError">Trigger client side error</button> </ChildContent> <ErrorContent>An error has occurred.</ErrorContent> </ErrorBoundary> @code { public async Task TriggerHttpClientErrorAsync() => await HttpClient.GetAsync("https://blazorschool.com"); public void TriggerClientSideError() => throw new Exception("Blazor School"); }
In this example, there are 2 buttons to trigger an HTTP error and a client-side error. However, neither of these errors will be caught by the ErrorBoundary
component, even though they appear to be inside it. This is because the TriggerHttpClientErrorAsync
and TriggerClientSideError
methods are being executed by the TriggerErrorOutsideScope
component, rather than executed inside the ErrorBoundary
component.
After learning how to handle errors, it's important to keep a few things in mind while developing websites:
try-catch
block in C# code to handle known errors and take appropriate action. If you cannot take any action against the error, then avoid using the try-catch
block and let the error handler do its job.CurrentException
object to your logging API.In Blazor WebAssembly, you cannot catch first request errors.