Unhandled Exceptions in Blazor Server with Error Boundaries
Tags: blazor, csharp, dotnet
Published: 2021-07-21 15:27:00

---
title: Unhandled Exceptions in Blazor Server with Error Boundaries
published: true
date: 2021-07-21 14:54:52 UTC
tags: blazor, csharp, dotnet
canonical_url: https://www.daveabrock.com/2021/07/21/blazor-error-boundaries/
---

_This post originally appeared on the [Telerik Developer Blog](https://www.telerik.com/blogs/work-unhandled-exceptions-gracefully-blazor-server-dotnet-6-error-boundaries)._

In Blazor—especially Blazor Server—there is no such thing as a small unhandled exception. When Blazor Server detects an unhandled exception from a component, ASP.NET Core treats it as a fatal exception. Why?

Blazor Server apps implement data processing statefully so that the client and server [can have a "long-lived relationship](https://docs.microsoft.com/en-us/aspnet/core/blazor/security/server/threat-mitigation?view=aspnetcore-5.0)." To accomplish this, Blazor Server creates a circuit server-side, which indicates to the browser how to respond to events and what to render. When an unhandled exception occurs, Blazor Server treats it as a fatal error because the circuit hangs in an undefined state, which can potentially lead to usability or security problems.

As a result, your app is as good as dead, loses its state, and your users are met with an undesirable _An unhandled error has occurred_ message, with a link to reload the page.

![](https://www.daveabrock.com/content/images/2021/07/unhandled-exception.png)

In many cases, unhandled exceptions can be out of a developer's control—like when you have an issue with third-party code. From a practical perspective, it's not realistic to assume components will _never_ throw.

Here's an example that [a Blazor developer](https://github.com/dotnet/aspnetcore/issues/26953) posted on GitHub: let's say I've got two components: `MyNicelyWrittenComponent`, which I wrote, and a third-party component, `BadlyWrittenComponent`, which I did not write. (Badly written components rarely identify themselves so well, but I digress.)

```html
<div>
   <MyNicelyWrittenComponent>
   <BadlyWrittenComponent Name="null">
</div>

```

What if the third-party code is coded like this?

```csharp
@code {
    [Parameter] public string Name { get; set; }
}
<p>@Name.ToLower()</p>
```

While I can reasonably expect `BadlyWrittenComponent` to blow up, the circuit is broken, and I can't salvage `MyNicelyWrittenComponent`. This begs the question: if I get an unhandled exception from a single component, why should my entire app die? As a Blazor developer, you're left spending a lot of time hacking around this by putting `try` and `catch` blocks around every single method of your app, leading to performance issues—especially when thinking about cascading parameters.

If you look at our front-end ecosystems, like React, they [use Error Boundaries](https://reactjs.org/docs/error-boundaries.html) to catch errors in a component tree and display a fallback UI when a failure occurs for a single component. When this happens, the errors are isolated to the problematic component, and the rest of the application remains functional.

Fear no more: with .NET 6 Preview 4, the ASP.NET Core team introduced [Blazor error boundaries](https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/#blazor-error-boundaries). Inspired by Error Boundaries in React, it attempts to catch recoverable errors that can't permanently corrupt state—and like the React feature, it also renders a fallback UI.

From the [Error Boundaries design document](https://github.com/dotnet/aspnetcore/issues/30940#issue-831895900), the team notes that its primary goals are to allow developers to provide fallback UIs for component subtrees if an exception exists, allow developers to provide cleaner fallback experiences, and provide more fine-grained control over how to handle failures. This capability won't catch all possible exceptions but most common scenarios such as the various lifecycle methods (like `OnInitializedAsync`, `OnParametersSetAsync`, and `OnAfterRenderAsync` ,  and rendering use cases with `BuildRenderTree`.

In this post, I'll help you understand what Error Boundaries are—and just as importantly, what they aren't.

## Get Started: Add Error Boundaries to the Main Layout

To get started, let's use a quick example. Let's say I know a guy—[let's call him MAUI Man](https://twitter.com/Telerik/status/1408063115875389455)—that runs a surf shop. In this case, MAUI Man has a Blazor Server app where he sells surfboards and t-shirts. His developer, Ed, wrote a `ProductService` that retrieves this data.

![](https://www.daveabrock.com/content/images/2021/07/maui-man-surf-shop.jpg)

Let's say the Surfboard API is having problems. Before the error boundary functionality was introduced, my component would fail to process the unhandled exception, and I'd see the typical error message at the bottom of the page.

![](https://www.daveabrock.com/content/images/2021/07/maui-man-old-error-message.jpg)

Let's use the new `ErrorBoundary` component instead. To do this, navigate to the `MainLayout.razor` file. (If you aren't familiar, the `MainLayout` component is your Blazor app's default layout.) In this file, surround the `@Body` declaration with the`ErrorBoundary` component. If an unhandled exception is thrown, we'll render a fallback error UI.

```html
@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

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

On the home page where we call the Surfboard API, we see the default error UI.

![](https://www.daveabrock.com/content/images/2021/07/surfboards-error.jpg)

The default UI does not include any content other than  _An error has occurred_. Even in development scenarios, you won't see stack trace information. Later in this post, I'll show you how you can include it.

This UI renders an empty `<div>` with a `blazor-error-boundary` CSS class. You can override this in your global styles if desired, or even replace it altogether. In the following example, I can customize my `ErrorBoundary` component. In this example, I include the `@Body` inside a `RenderFragment` called `ChildContent`. This content displays when no error occurs. Inside `ErrorContent`—which, for the record, is _technically_ a `RenderFragment<Exception>`—displays when there's an unhandled error.

```html
<ErrorBoundary>
  <ChildContent>
     @Body
  </ChildContent>
  <ErrorContent>
     <p class="my-custom-class">Whoa, sorry about that! While we fix this problem, buy some shirts!</p>
  </ErrorContent>
</ErrorBoundary> 
```

How else can you work with the `ErrorBoundary`? Let's explore.

## Exploring the ErrorBoundary Component 

Aside from the `ChildContent` and `ErrorContent` fragments, the out-of-the-box `ErrorBoundary` component also provides a `CurrentException` property—it's an `Exception` type you can use to get stack trace information. You can use this to add to your default error message (which should only be exposed in development environments for security reasons).

Most importantly, the `ErrorBoundary` component allows you to call a `Recover` method, which resets the error boundary to a "non-errored state." By default, the component [handles up to 100 errors](https://github.com/dotnet/aspnetcore/blob/223d6e527cc98ce13fe45e4e14911013dd5ad807/src/Components/Components/src/ErrorBoundaryBase.cs#L31) through its `MaximumErrorCount` property. The `Recover` method [does three things for you](https://github.com/dotnet/aspnetcore/blob/223d6e527cc98ce13fe45e4e14911013dd5ad807/src/Components/Components/src/ErrorBoundaryBase.cs#L42): it resets the component's error count to `0`, clears the `CurrentException` and calls `StateHasChanged`. The `StateHasChanged` call [notifies components that the state has changed](https://docs.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-5.0#state-changes-statehaschanged) and typically causes your component to be rerendered.

We can use this to browse to our `Shirts` component when we have an issue with the Surfboard API (or the other way around). Since the boundary is set in the main layout, at first we see the default error UI on every page. We can use the component's `Recover` method to reset the error boundaries on subsequent page navigations.

```csharp
@code {
    ErrorBoundary errorBoundary;

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

As a best practice, you'll typically want to define your error boundaries at a more granular level. Armed with some knowledge about how the `ErrorBoundary` component works, let's do just that.

## Dealing with Bad Data

It's quite common to experience issues with getting and fetching data, whether it's from a database directly or from APIs we're calling (either internal or external APIs).  This can happen for a variety of reasons: from a flaky connection, an API's breaking changes, or just inconsistent data.

In my `ShirtList` component, we call off to a `ShirtService`. Here's what I have in `ShirtList.razor.cs`:

```csharp
using ErrorBoundaries.Data;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ErrorBoundaries.Pages
{
    partial class ShirtList : ComponentBase
    {
        public List<Shirt> ShirtProductsList { get; set; } = new List<Shirt>();

        [Inject]
        public IProductService ShirtService { get; set; }

        protected override async Task OnInitializedAsync()
        {
            ShirtProductsList = await ShirtService.GetShirtList();
        }
    }
}
```

In my `ShirtList.razor` file, I'm iterating through the `ShirtProductsList` and displaying it. (If you're worked in ASP.NET Core before you've done this anywhere between five million and ten million times.)

```html
<table class="table">
 <thead>
     <tr>
         <th>ID</th>
         <th>Name</th>
         <th>Color</th>
         <th>Price</th>
     </tr>
  </thead>
  <tbody>
     @foreach (var board in ShirtProductsList)
     {  
         <tr>
            <td>@board.Id</td>
            <td>@board.Name</td>
            <td>@board.Color</td>
            <td>@board.Price</td>
          </tr>   
        }
  </tbody>
</table>
```

Instead, I can wrap the display in an `ErrorBoundary` and catch the error to display a message instead. (In this example, to avoid repetition I'll show the `<tbody>` section for the Razor component.)

```html
<tbody>
   @foreach (var board in ShirtProductsList)
   {
        <ErrorBoundary @key="@board">
            <ChildContent>
                <tr>
                   <td>@board.Id</td>
                   <td>@board.Name</td>
                   <td>@board.Color</td>
                   <td>@board.Price</td>
                 </tr>   
             </ChildContent>
             <ErrorContent>
                 Sorry, I can't show @board.Id because of an internal error.
             </ErrorContent>
         </ErrorBoundary>        
     }
</tbody>
```

In this situation, the `ErrorBoundary` can take a `@key`, which in my case is the individual `Surfboard`, which lives in the  `board` variable. The `ChildContent` will display the data just as before, assuming I don't get any errors. If I encounter any unhandled errors, I'll use `ErrorContent` to define the contents of the error message. In my case, it provides a more elegant solution than placing generic `try` and `catch` statements all over the place.

## What Error Boundaries _Isn't_

I hope this gentle introduction to Blazor Error Boundaries has helped you understand how it all works. We should also talk about which use cases aren't a great fit for Error Boundaries.

Blazor Error Boundaries is not meant to be a global exception handling mechanism for any and all unhandled errors you encounter in your apps. You should be able to log all uncaught exceptions using the `ILogger` interface.

While you _can_ mark all your components with error boundaries and ignore exceptions, you should take a more nuanced approach to your application's error handling. Error Boundaries are meant for control over your specific component's unhandled exceptions and not a quick way to manage failures throughout your entire application.  

## Conclusion

While it's been nice to see the growth of Blazor over the several years, it's also great to see how the ASP.NET Core team isn't afraid to add new features by looking around at other leading front-end libraries—for example, the CSS isolation was [inspired by Vue](https://vue-loader.vuejs.org/guide/scoped-css.html#scoped-css) and Error Boundaries is [taking cues from what React has done](https://reactjs.org/docs/error-boundaries.html). Not only is Blazor built on open web technologies, but it's also not afraid to look around the community to make things better.

Do you find this useful? Will it help you manage component-specific unhandled exceptions? Let us know in the comments.