Un ErrorBoundary es un componente que captura excepciones no controladas en los componentes descendientes y muestra una interfaz alternativa.
Sin tratamiento, una excepción puede mostrar la interfaz de error de Blazor. En Interactive Server, una excepción no controlada puede cerrar el circuito porque ya no se puede garantizar su estado.
Vamos a contener los fallos previsibles, mostrar una alternativa útil y registrar los detalles sin exponerlos al usuario.
Capturar errores esperables con try-catch
La primera línea de defensa es la programación defensiva estándar de C#. Si sabes que una operación es peligrosa (como llamar a una API), envuélvela.
private string? ErrorMessage;
protected override async Task OnInitializedAsync()
{
try
{
Productos = await ProductoService.GetProductosAsync();
}
catch (HttpRequestException ex)
{
// Error de red (API caída, 404, 500)
ErrorMessage = "No pudimos conectar con el servidor. Inténtalo más tarde.";
Logger.LogError(ex, "Error obteniendo productos");
}
catch (Exception ex)
{
// Error inesperado
ErrorMessage = "Ocurrió un error grave.";
Logger.LogError(ex, "Error inesperado obteniendo productos");
}
}En la vista, renderizamos condicionalmente:
@if (!string.IsNullOrEmpty(ErrorMessage))
{
<div class="alert alert-danger">@ErrorMessage</div>
}
else if (Productos == null)
{
<p>Cargando...</p>
}
else
{
}Esto funciona bien para lógica de negocio. Pero, ¿qué pasa si el error ocurre durante el renderizado?
Si tienes un bug en tu HTML (ej: @usuario.Nombre y usuario es null), el try-catch del OnInitialized no te salvará.
Proteger la interfaz con ErrorBoundary
ErrorBoundary envuelve marcado y componentes Razor. Si un descendiente lanza una excepción no controlada durante su ciclo de vida, renderizado o manejo de eventos, muestra un contenido alternativo.
Funciona igual que un bloque try-catch, pero alrededor de etiquetas HTML o Componentes Razor. Si cualquier componente hijo lanza una excepción, el ErrorBoundary la captura y muestra una interfaz alternativa en lugar de romper toda la aplicación.
Implementación Básica
Podemos envolver zonas peligrosas de nuestra aplicación.
<ErrorBoundary>
<ChildContent>
<WidgetClima />
</ChildContent>
<ErrorContent>
<div class="alert alert-warning">
⚠️ El widget del clima no está disponible temporalmente.
</div>
</ErrorContent>
</ErrorBoundary>Fíjate que usamos dos RenderFragments:
ChildContent: El contenido normal (camino feliz).ErrorContent: El contenido de fallback (camino de error).
Implementación global en MainLayout
Una estrategia posible es envolver el cuerpo de la página en MainLayout.razor. Conviene mantener los límites tan acotados como sea práctico, para que un widget defectuoso no sustituya una página completa.
<main>
<article class="content px-4">
<ErrorBoundary @ref="errorBoundary">
<ChildContent>
@Body
</ChildContent>
<ErrorContent>
<div class="error-page">
<h3>😵 ¡Ups! Algo salió mal.</h3>
<button class="btn btn-primary" @onclick="Recover">
Intentar de nuevo
</button>
</div>
</ErrorContent>
</ErrorBoundary>
</article>
</main>
@code {
private ErrorBoundary? errorBoundary;
private void Recover()
{
// Este método resetea el componente y vuelve a intentar renderizar el ChildContent
errorBoundary?.Recover();
}
}El método Recover() limpia el estado de error e intenta renderizar de nuevo el contenido. Llámalo después de una acción que pueda resolver la causa o al navegar a otra página; si el mismo fallo continúa, volverá a lanzar la excepción.
En una Blazor Web App, un ErrorBoundary situado en un layout estático solo actúa durante SSR estático. Para capturar errores de eventos interactivos, el límite debe encontrarse dentro del mismo árbol interactivo o la aplicación debe usar interactividad global.
Registrar los errores
Capturar el error visualmente está bien para el usuario, pero nosotros como desarrolladores necesitamos saber qué ha pasado.
Blazor se integra con el sistema de Logging de .NET (ILogger).
En Blazor Server: Los logs van a la consola del servidor (o a Application Insights, Serilog, etc., si lo configuras). En Blazor WebAssembly: Los logs van a la Consola de Desarrollador del Navegador (F12).
@inject ILogger<MiComponente> Logger
@code {
try { ... }
catch (Exception ex)
{
// Esto escribe en la consola del navegador en WASM
Logger.LogError(ex, "Error crítico al procesar el pedido {Id}", pedidoId);
}
}Errores detallados durante el desarrollo
Aunque usemos ErrorBoundary, a veces queremos loguear cualquier excepción que ocurra, incluso las que rompen el circuito, para enviarlas a un servicio externo (como Sentry o Azure App Insights).
En WebAssembly, las excepciones no controladas se escriben en la consola del navegador. Para SSR y circuitos de servidor, activa los detalles solo en desarrollo, porque pueden contener datos sensibles:
// Program.cs: errores detallados durante SSR
builder.Services.AddRazorComponents(options =>
options.DetailedErrors = builder.Environment.IsDevelopment());Para los circuitos interactivos también puedes establecer "DetailedErrors": true en appsettings.Development.json.
Excepciones vs Errores de Negocio
Es importante diferenciar:
- Excepción (Bug/Crash): “NullReferenceException”, “Database Timeout”. Esto se maneja con
ErrorBoundaryotry-catchglobal. - Error de Negocio (Validación): “El usuario no tiene saldo”, “Email duplicado”.
Los resultados esperables, como «saldo insuficiente» o «correo duplicado», suelen representarse mejor con un resultado explícito o un mensaje de validación. Reserva las excepciones para condiciones que realmente interrumpen el flujo normal.
- Mal:
throw new Exception("Saldo insuficiente");(ElErrorBoundaryocultará la página y mostrará “Ups”). - Bien: Mostrar un
<div class="alert">Saldo insuficiente</div>y dejar que el usuario corrija la acción.