After the release of .NET 8, we can use this new interface to handle exceptions globally in our project. The IExceptionHandler
interface has a single method member named TryHandleAsync
:
ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken);
With this method, we try to handle the specified exception asynchronously within the ASP.NET Core pipeline. Basically, we can use our existing error-handling logic inside this method and everything should work as before. One great advantage of this implementation is that we register the class, where we handle our exceptions, as a service, which means we can use other services inside that class. If you remember our previous implementation, we had to retrieve the ILoggerManager
service first and then send it as an argument to the extension method.
Now, we don’t have to do that, we can simply inject the ILoggerManager
service and use it while handling exceptions.
To start, let’s create a new GlobalExceptionHandler
class inside the main project:
using Microsoft.AspNetCore.Diagnostics; namespace CompanyEmployees; public class GlobalExceptionHandler : IExceptionHandler { public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { } }
You can see that our class inherits from the IExceptionHandler
interface and implement the TryHandleAsync
method. Now, let’s fully implement the method and modify the class:
public class GlobalExceptionHandler : IExceptionHandler { private readonly ILoggerManager _logger; public GlobalExceptionHandler(ILoggerManager logger) { _logger = logger; } public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken) { httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; httpContext.Response.ContentType = "application/json"; _logger.LogError($"Something went wrong: {exception.Message}"); await httpContext.Response.WriteAsJsonAsync(new ProblemDetails { Title = "An error occurred", Status = httpContext.Response.StatusCode, Detail = "Internal Server Error.", Type = exception.GetType().Name }); return true; } }
This time, we inject the ILoggerManager
service and use it inside the TryHandleAsync
method to log our message. The rest of the code is familiar with a difference that we have to return true from this method to state that the execution in the pipeline is over. With this in place, we can register this class as a service, and register a handler middleware in the Program
class:
builder.Services.AddAutoMapper(typeof(Program)); builder.Services.AddExceptionHandler<GlobalExceptionHandler>(); builder.Services.AddControllers() .AddApplicationPart(typeof(CompanyEmployees.Infrastructure.Presentation.AssemblyReference).Assembly); builder.Host.UseSerilog((hostContext, configuration) => { configuration.ReadFrom.Configuration(hostContext.Configuration); }); var app = builder.Build(); //var logger = app.Services.GetRequiredService<ILoggerManager>(); //app.ConfigureExceptionHandler(logger); app.UseExceptionHandler(opt => { });
Here, we use the AddExceptionHanlder
method to register our GlobalExceptionHandler
class as a service. Also, we hide our old code and call the UseExceptionHanlder
method to add the middleware to the pipeline. That’s it. You can test this out with the same Postman request, and everything should work the same.