44
loading...
This website collects cookies to deliver better user experience
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.)<div>
<MyNicelyWrittenComponent>
<BadlyWrittenComponent Name="null">
</div>
@code {
[Parameter] public string Name { get; set; }
}
<p>@Name.ToLower()</p>
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.OnInitializedAsync
, OnParametersSetAsync
, and OnAfterRenderAsync
, and rendering use cases with BuildRenderTree
.ProductService
that retrieves this data.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 theErrorBoundary
component. If an unhandled exception is thrown, we'll render a fallback error UI.@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<div class="main">
<div class="content px-4">
<ErrorBoundary>
@Body
</ErrorBoundary>
</div>
</div>
</div>
<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.<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>
ErrorBoundary
? Let's explore.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).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 through its MaximumErrorCount
property. The Recover
method does three things for you: 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 and typically causes your component to be rerendered.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.@code {
ErrorBoundary errorBoundary;
protected override void OnParametersSet()
{
errorBoundary?.Recover();
}
}
ErrorBoundary
component works, let's do just that.ShirtList
component, we call off to a ShirtService
. Here's what I have in ShirtList.razor.cs
: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();
}
}
}
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.)<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>
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.)<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>
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.ILogger
interface.