14

Blazor: Why ErrorBoundary Doesn't Catch Exceptions in Async Events

Understand why Blazor's ErrorBoundary doesn't work with async event handlers and how to handle exceptions properly.

⚠️ The Problem: ErrorBoundary doesn't catch async event exceptions

In Blazor, the <ErrorBoundary> component is designed to catch unhandled exceptions in the UI. However, a common pitfall is that it does not catch exceptions thrown inside async methods triggered by user events like OnClick, OnFilesChanged, or OnInput.

This behavior is confirmed and tracked in the official GitHub issue:
👉 dotnet/aspnetcore#56413


Reproducing the Problem

Here's a minimal example where component test.razor have MudFileUpload :

<MudPaper Class="pa-4">
    <MudStack Row Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
        <MudFileUpload T="IBrowserFile" OnFilesChanged="LoadFiles" AppendMultipleFiles=true>
            <ActivatorContent>
                <MudButton Variant="Variant.Filled"
                           Color="Color.Primary"
                           StartIcon="@Icons.Material.Filled.CloudUpload">
                   Import files
                </MudButton>
            </ActivatorContent>
        </MudFileUpload>
    </MudStack>
    <BreadcrumbSeparator />
</MudPaper>

The component uses MainLayout.razor and inside it there is our ErrorBoundary

 <MudMainContent >
     <MudContainer Fixed=true>
         <ErrorBoundary>
             <ChildContent>
                 @Body
             </ChildContent>
             <ErrorContent Context="exception">
                 <BoundaryCustomComponent ExceptionMessage="@exception" />
             </ErrorContent>
         </ErrorBoundary>
     </MudContainer>
 </MudMainContent>

And the function :

private async Task LoadFiles(InputFileChangeEventArgs e)
{
    // Simulate an exception
    throw new InvalidOperationException("Something went wrong during upload.");
}

⚠️ The exception above will NOT be caught by the ErrorBoundary, because it happens in an async event handler outside the component rendering lifecycle.

Solution 1: Explicit try/catch block

To catch exceptions in async events, wrap the logic in a try/catch block:

private async Task LoadFiles(InputFileChangeEventArgs e)
{
    try
    {
        // Simulate exception
        throw new InvalidOperationException("Something went wrong.");
    }
    catch (Exception ex)
    {
        Snackbar.Add($"Error: {ex.Message}", Severity.Error);
    }
}

Solution 2: Centralize error and loading handling

To avoid repeating the same try/catch and loading logic, you can centralize it in a reusable helper like this:

public async Task RunWithLoadingAsync(Func<Task> action, Action? onSuccess, Action<Exception>? onError)
{
    try
    {
        IsLoading = true;
        await InvokeAsync(StateHasChanged); // force UI update
        await action();
        onSuccess?.Invoke();
    }
    catch (Exception ex)
    {
        onError?.Invoke(ex);
    }
    finally
    {
        IsLoading = false;
        await InvokeAsync(StateHasChanged); // force UI update again
    }
}

This Method:

  • Displays a loading spinner (if you have one in your UI) while the async operation runs
  • Catches and handles exceptions gracefully
  • Optionally triggers success or error callbacks
  • Keeps your event handlers clean and focused

📌 Summary

The built-in ErrorBoundary component in Blazor does not catch exceptions thrown inside asynchronous event handlers (like OnClick, OnFilesChanged, etc.). This is because those exceptions happen outside the component's rendering pipeline, and thus bypass the ErrorBoundary.

To handle these cases properly, you need to wrap your async code inside a try/catch block manually. However, doing this in every event handler quickly becomes repetitive and error-prone.

A cleaner and more maintainable solution is to use a reusable helper method like RunWithLoadingAsync. This approach centralizes error handling and UI state management (such as displaying a loading spinner), keeping your component code concise and consistent.