Error handling

Handling errors properly is essential in building a robust website in Blazor WebAssembly. Error handlers provide a friendly message to the users and allow the developers to collect useful data about an incident. In this tutorial, you will discover:

  • Why do you need to handle errors?
  • What to do when handling an error?
  • Error types.
  • Catching the error.
  • Some common actions against an error.
  • What is an error handler?
  • Why do you need a custom error handler?
  • Build a custom error handler.
  • Tracking error.
  • Common mistakes.
  • Best practice.
You can download the example code used in this topic on GitHub.

Why do you need to handle errors?

  • Inaccessible of console log in the production.

A lot of programmers often start out using the Console.WriteLine because that is the default output in most development environments. However, once you deployed your website to a production environment, you no longer have access to the console log. That's because the code is now running on the client browser. Unless you handled the errors to collect errors in a centralized location, you won't have any visibility into them. In order to understand the user experience and how errors can affect it, you need to track errors centrally. That means not only tracking caught errors, but uncaught or run-time errors as well. In order to catch uncaught errors, you need to implement an error handler, which we will describe next.

  • Website credibility.

An error in the production is inevitable, even for global enterprise websites of Google, Microsoft, Amazon… Imagine a user enters your website, provides some information then presses submit, an error page with the message 404 appears. The user might not be a technical person, they don't know what is going on and will assume your business is not worth trying or is a scam website. The situation is totally avoidable if you had handled errors better by providing a friendly message, for example.

  • Free bug hunters!

Your website might have some loyalty users. Those users are always willing to give you useful information on a bug that they found. However, if you don't provide an easy way to report bugs, you won't get much useful information from them. By providing an error report page, you can collect the error information for investigation.


What to do when handling an error?

Handling an error including 2 steps:

  1. Catching the error: In this step, you are able to detect when something went wrong in your website is the least you can do. You can also improve this step by able to answer the question “What went wrong?”.
  2. Act accordingly: In this step, you give the user a friendly message about the error is the least you can do. You can also improve this step by collect the error data or resume the operation and more.

Error types

There are 3 types of errors:

  1. Server Faults: This type of error is handled by the host, and Blazor WebAssembly can do nothing about this.
  2. HTTP Errors: This type of error is not directly come from your Blazor WebAssembly website, but the API side. You can catch those errors by Blazor WebAssembly.
  3. Client Side Errors: This type of error usually come from components. Sometimes by C# code, sometimes by JavaScript. You can catch those errors by Blazor WebAssembly.
Keep in mind that any error/exception that comes from the JavaScript code won't automatically caught by default, you need to catch them manually by a try catch (C# code) block.

Catching the error

You can catch errors by using the ErrorBoundary component or your custom component. You can catch errors from 4 scopes:

  1. Global: Catches any error from the website.
  2. Layout: Catches any error throws from within the layout.
  3. Component: Catches any error throws from within the component.
  4. HttpClient: Catches any error that causes by the API.

Global error catching

Open your wwwroot/index.html. You will see the following element:

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

You can update the content of this element to show a message whenever an error occurs in your website.

Catching errors from layout

A layout is a component with @inherits LayoutComponentBase. A layout usually put in the Shared folder. You can learn more about layout at Website Layout. In Blazor WebAssembly, the default layout is located at Shared/MainLayout.razor, you will see the following tag in this component:

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

You can use an error handler component to catch errors that come from this layout. For example, we use the ErrorBoundary component as follows:

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

Component error catching

You can catch an error that throws within a component by wrapping the component inside an error handler component. Assuming we have the following TriggerError component:

@inject HttpClient HttpClient

<h3>TriggerError</h3>
<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");
}

Then in another component, wrap the TriggerError component inside the ErrorBoundary component as follows:

<ErrorBoundary>
    <TriggerError/>
</ErrorBoundary>

Catching HTTP errors by HttpClient wrapper technique

You can use the HttpClient wrapper technique to catch any error that causes by the API. Assuming we have the following

ExceptionRecorderService and BlazorSchoolHttpClientWrapper class:

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;
    }
}

In the Program.cs:

builder.Services.AddScoped<ExceptionRecorderService>();
builder.Services.AddHttpClient<BlazorSchoolHttpClientWrapper>((sp, httpClient) => httpClient.BaseAddress = new(builder.HostEnvironment.BaseAddress));

Then you can inject the BlazorSchoolHttpClientWrapper into any component and use the ExceptionRecorderService to get the error detail. For example:

@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://blazorschool.com");
    }

    public void RefreshUI(object? sender, NotifyCollectionChangedEventArgs eventArgs)
    {
        InvokeAsync(StateHasChanged);
    }

    public void Dispose()
    {
        ExceptionRecorderService.Exceptions.CollectionChanged -= RefreshUI;
    }
}

Error scope delegation

An unhandled error will delegate the handler to the next scope in sequence. An error handler in the component scope will be the first one to handle the error, if there is none, it will be delegated to the layout scope, if there is no error handler in the layout, then the error will be handled by the global error handler. If the error is handled, it will not delegate to the next scope. The HttpClient error is a standalone scope, you either handle them or not handle them, an HttpClient error will not halt your website. The following image illustrates the error scope delegation:

scope-delegation.png


Some common actions against an error

Take actions against an error in the second step of handling an error. In this section, we will introduce you 2 common actions against an error, they are:

  1. Display the error message to the user.
  2. Resume the operation.

Display the error message to the user

When an error occurs, the users might not need what is the error, but they certainly need a friendly message to soothe the experience with your website. You can display a friendly message when an error occurs by using the ErrorContent component as follows:

<ErrorBoundary>
    <ChildContent>
        <TriggerError/>
    </ChildContent>
    <ErrorContent Context="ex">
        Caught an exception from the TriggerError componennt. The exception message is @ex.Message.
    </ErrorContent>
</ErrorBoundary>

Resume the operation

An error occur will halt the current operation of the user. You can let the users ignore the error and continue using your website by calling Recover method of the ErrorBoundary component as follows:

<ErrorBoundary @ref="ComponentErrorBoundary">
    <ChildContent>
        <TriggerError/>
    </ChildContent>
    <ErrorContent Context="ex">
        An error has occurred. <button @onclick="_ => ComponentErrorBoundary.Recover()">Continue</button>
    </ErrorContent>
</ErrorBoundary>

@code {
    public ErrorBoundary ComponentErrorBoundary { get; set; } = default!;
}

What is an error handler?

An error handler is a component that extends the ErrorBoundary or ErrorBoundaryBase component and be handle any error in a scope. For example, if an error handler is put at the component scope, it will handle any error from within the scope. ErrorBoundary is the built-in error handler component, you can create your own error handler component as well.


Why do you need a custom error handler?

Sometimes, you need to have more control over the handling process. By default, if your website throws more than 100 exceptions, it will be considered as critical and fallback to the global error handler. By default, you don't have access to the current exception (you can show the detail but that's it) and therefore you cannot send the exception back to your logging API. Build a custom error handler provide you the access to the current exception and you can send the exception back to the logging API for example or take other actions.


Build a custom error handler

You can create a custom error handler by creating a component that extends the ErrorBoundary or ErrorBoundaryBase component. Extending the ErrorBoundaryBase gives you more control when handling an error. Here is our example custom error handler:

public class BlazorSchoolErrorBoundary : ErrorBoundaryBase
{
    [Inject]
    public ExceptionRecorderService ExceptionRecorderService { get; set; } = default!;

    public BlazorSchoolErrorBoundary()
    {
        MaximumErrorCount = 2;
    }

    protected override Task OnErrorAsync(Exception exception)
    {
        ExceptionRecorderService.Exceptions.Add(exception);

        return Task.CompletedTask;
    }

    protected void RecoverAndClearErrors()
    {
        Recover();
        ExceptionRecorderService.Exceptions.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.AddAttribute(3, "class", "text-danger border border-danger p-3");
                builder.AddContent(4, "Blazor School Custom Error Boundary.");
                builder.AddContent(5, __innerBuilder =>
                {
                    __innerBuilder.OpenElement(6, "button");
                    __innerBuilder.AddAttribute(7, "type", "button");
                    __innerBuilder.AddAttribute(8, "class", "btn btn-link");
                    __innerBuilder.AddAttribute(9, "onclick", RecoverAndClearErrors);
                    __innerBuilder.AddContent(10, "Continue");
                    __innerBuilder.CloseElement();
                });
                builder.CloseElement();
            }
        }
    }
}

When implementing a custom error handler, you need to pay attention to:

  • MaximumErrorCount: If there is x number of exceptions in the error handler, then it will fall back to the global exception handler. In the example, we set it to 2 in the constructor.
  • OnErrorAsync: What to do when an error occurs. In the example, we record exception with the ExceptionRecorderService service in the OnErrorAsync method.
  • BuildRenderTree: Render the UI when an error occurs. In the example, we print out the text "Blazor School Custom Error Boundary" and a button to continue the operation.

Once you have your custom error handler component, you can use it as the ErrorBoundary component. For example:

<BlazorSchoolErrorBoundary>
    <ChildContent>
        <TriggerError />
    </ChildContent>
    <ErrorContent>
        <TriggerError />
        <div>Throw more error and I will crash.</div>
    </ErrorContent>
</BlazorSchoolErrorBoundary>
<BlazorSchoolErrorBoundary>
    <TriggerError />
</BlazorSchoolErrorBoundary>

Tracking error

In most of enterprise websites, you will see an error report page for the user to report problems to the developers. With the custom error handler, you can access the CurrentException property. You can send this object to the report problem page to collect the user's data on a defect.


Common mistakes

When handling an error, make sure you don't make the common mistake we are going to talk about in this section.

Throw error outside of error handler's scope

Based on the data of the Blazor School Discord Community, we see most of the people throw exceptions outside of the error handler's scope and expecting the error handler will handling the exception. Consider the following code in the TriggerErrorOutsideScope component:

@inject HttpClient HttpClient

<h3>TriggerErrorOutsideScope</h3>
<ErrorBoundary>
    <button class="btn btn-primary" type="button" @onclick='TriggerHttpClientErrorAsync'>Trigger HttpClient error</button>
    <button class="btn btn-primary" type="button" @onclick="TriggerClientSideError">Trigger client side error</button>
</ErrorBoundary>

@code {
    public async Task TriggerHttpClientErrorAsync() => await HttpClient.GetAsync("https://blazorschool.com");
    public void TriggerClientSideError() => throw new Exception("Blazor School");
}

In the example, there are 2 buttons to trigger an HTTP error and the client side error. Both won't be caught by the ErrorBoundary component although they appear to be inside the ErrorBoundary component. That is, because the TriggerHttpClientErrorAsync and TriggerClientSideError is being executed by the component TriggerErrorOutsideScope component rather than executed inside the ErrorBoundary component.


Best practice

You have learnt how to handle errors, here are a few things you should keep in mind while developing website:

  • Use a try catch block in C# code to handle the know errors. Act accordingly. If you are not able to take action against it, then don't use the try catch block and let the error handler do the job.
  • In every layout you should have an error handler to trap all unhandled errors and act accordingly rather than always delegate to the global error handler and force the user to reload everytime there is an error.
  • Check for type of error in the error handler and act accordingly. For example, if there is an error from the API (HTTP error) you can use the status code to take necessary action. 401 Unauthorized error then you can redirect the user to the login page. 500 Internal Server Error then you can send the CurrentException object to your logging API.
  • For all other unhandled errors, send the error detail back to the logging API. You can use any third party error providers or build yourself one. You can then look at those logs and necessary changes to the website.
  • An ideal error report page should be a static html page with minimal functionality.
BLAZOR SCHOOL
Designed and built with care by our dedicated team, with contributions from a supportive community. We strive to provide the best learning experience for our users.
Docs licensed CC-BY-SA-4.0
Copyright © 2021-2025 Blazor School
An unhandled error has occurred. Reload 🗙