Author : MD TAREQ HASSAN | Updated : 2020/08/21
Handling exceptions in Blazor
- As of August, 2020: Blazor does not support global exception handling
- We will handle exception/error in service and log related information. Then rethrow
AppException
- Each page component will catch
AppException
and will show error (logging is done in service, so page would not do logging) - Links:
AppException class
Exceptions/AppException.cs
/// <summary>
/// App realted exception. Page components will catch this exception and show error to UI.
/// </summary>
public class AppException : Exception
{
/// <summary>
/// Exception with message and inner exception
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
public AppException(string message, Exception exception) : base(message, exception)
{
}
/// <summary>
/// Exception with message only
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
public AppException(string message) : base(message)
{
}
}
Exception handling and logging in service
Services/IFooService.cs
/// <summary>
/// Service/domain interface
/// </summary>
public interface IFooService
{
// ... ... ...
/// <summary>
/// Find foo data list.
/// </summary>
/// <returns>List<FooModel> - foo data list</returns>
Task<FooModel> FetchFooAsync();
// ... ... ...
}
Services/FooService.cs
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.Http;
/// <summary>
/// Implementation of service contract/interface
/// </summary>
public class FooService : IFooService
{
private readonly FooDbContext _dbContext;
private readonly IMapper _mapper;
// ... ... ...
public FooService(FooDbContext dbContext, IMapper mapper //, ... ... ...)
{
_dbContext = dbContext;
_mapper = mapper;
// ... ... ...
}
/// <inheritdoc />
public async Task<FooModel> FetchFooAsync()
{
try
{
var entityList = await _dbContext.Foos.ToListAsync();
var fooList = _mapper.Map<List<FooModel>>(entityList);
return fooList;
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
throw new AppException("Error occured while fetching email data");
}
}
// ... ... ...
}
Handling and showing error in page component
Pages/Index.razor.cs
public class IndexBase : ComponentBase
{
// ... ... ...
[Inject]
protected IFooService FooService { get; set; }
protected string ErroMessage { get; set; } = string.Empty;
protected List<FooModel> FooList = default;
// ... ... ...
protected override async Task OnInitializedAsync()
{
Log.Information("Initializing required data");
ErroMessage = string.Empty;
try
{
FooList = await FooService.FetchFooAsync();
// ... ... ...
}
catch (AppException ex) // logging will be done in service
{
ErroMessage = ex.Message;
}
catch (Exception ex) // do logging
{
Log.Error(ex.Message, ex);
ErroMessage = "Unexpected error occured!";
}
}
// ... ... ...
}
Pages/Index.razor
@page "/"
@page "/foos"
@inherits IndexBase
// ... ... ...
<div class="...">
if (!string.IsNullOrWhiteSpace(ErroMessage))
{
<h3>@ErroMessage</h3>
}
else
{
@if (FooList == null || !FooList.Any())
{
<h3>Loading...</h3>
}
else
{
@foreach (var foo in FooList)
{
// use foo
// ... ... ...
}
}
}
</div>
Detailed error
Startup.cs
public class Startup
{
// ... ... ...
public void ConfigureServices(IServiceCollection services)
{
// ... ... ...
services.AddServerSideBlazor().AddCircuitOptions(options => { options.DetailedErrors = true; });
// ... ... ...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ... ... ...
}
}