How to Implement Global Exception Handling in ASP.NET Core Middleware


In this article, we will delve into the process of implementing global exception handling in ASP.NET Core middleware. By handling exceptions globally, we can streamline error management and enhance the robustness of our applications. We will explore common exceptions, demonstrate how to throw them when errors occur and catch these exceptions using a try-catch block within the middleware.
You can find more details about try-catch block here
I assume that you have already created a Web API project for implementation. If so, your project structure should look as follows:
Let’s create a folder named Exceptions
where we will develop custom exception classes. These classes will inherit from the Exception
class.
The first one will be BadRequestException
, which will inherit from the Exception
class in the System namespace.
namespace ExceptionHandlingInMiddleware.Exceptions;
public class BadRequestException : Exception
{
public BadRequestException(string message) : base(message)
{
}
}
The second one will be NotFoundException
namespace ExceptionHandlingInMiddleware.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException(string message):base(message)
{
}
}
The third one will be NotImplementedException
namespace ExceptionHandlingInMiddleware.Exceptions;
public class NotImplementedException : Exception
{
public NotImplementedException(string message) : base(message)
{
}
}
The last one will be UnauthorizedException
namespace ExceptionHandlingInMiddleware.Exceptions;
public class UnauthorizedException:Exception
{
public UnauthorizedException(string message):base(message)
{
}
}
Now that we have finished creating the custom exception classes, you can still create additional custom exception classes if needed.
After creating all the classes, your structure should look as follows:
Now, we need to integrate our exceptions into middleware. To do this, we will create a folder named Configurations
, where we will implement our middleware and utilize it in the Program.cs
file.
After creating the folder, we will create a middleware class named ExceptionMiddlewareConfiguration
and begin implementing it.
First, we will add a request delegate to intercept the request pipeline. For more details about the request pipeline, refer to the following:
namespace ExceptionHandlingInMiddleware.Configurations;
public class ExceptionMiddlewareConfiguration
{
private readonly RequestDelegate _next;
public ExceptionMiddlewareConfiguration(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception excp)
{
// we will implement exceptions here
}
}
}
Next, we need to check the type of exception being passed and determine the appropriate status code to return.
using ExceptionHandlingInMiddleware.Exceptions;
using System.Net;
using System.Text.Json;
namespace ExceptionHandlingInMiddleware.Configurations;
public class ExceptionMiddlewareConfiguration
{
private readonly RequestDelegate _next;
public ExceptionMiddlewareConfiguration(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception excp)
{
await ExceptionAsync(context, excp);
}
}
private static Task ExceptionAsync(HttpContext context, Exception ex)
{
// Here, the HTTP codes will be determined based on exceptions
HttpStatusCode statusCode;
string message = "Unexpected error";
// We need to identify the type of the exception
var excpType = ex.GetType();
// Let's check what kind of exceptions are passed
if (excpType == typeof(BadRequestException))
{
statusCode = HttpStatusCode.BadRequest;
message = ex.Message;
}
else if (excpType == typeof(NotFoundException))
{
statusCode = HttpStatusCode.NotFound;
message = ex.Message;
}
else if (excpType == typeof(Exceptions.NotImplementedException))
{
statusCode = HttpStatusCode.NotImplemented;
message = ex.Message;
}
else if (excpType == typeof(UnauthorizedException))
{
statusCode = HttpStatusCode.Unauthorized;
message = ex.Message;
}
else
{
statusCode = HttpStatusCode.InternalServerError;
message = ex.Message;
}
var result = JsonSerializer.Serialize(new { message = message });
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)statusCode;
return context.Response.WriteAsync(result);
}
}
As shown above, we can check the type of exception and return the appropriate status code.
Now, we need to inject our middleware class into the Program.cs
file.
To do this, let’s create a static class named ApplicationBuilderConfiguration
under the Configurations
folder.
namespace ExceptionHandlingInMiddleware.Configurations;
public static class ApplicationBuilderConfiguration
{
public static IApplicationBuilder ErrorHandler(this IApplicationBuilder applicationBuilder) => applicationBuilder.UseMiddleware();
}
Next, we need to call the ApplicationBuilderConfiguration
static class after Add.MapController()
in the Program.cs
file.using ExceptionHandlingInMiddleware.Configurations;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.ErrorHandler();
app.Run();
Now, it’s time to test our exception handling implementation. Let’s create a controller called TestController
and implement an HttpGet
API.
using ExceptionHandlingInMiddleware.Exceptions;
using Microsoft.AspNetCore.Mvc;
using NotImplementedException = ExceptionHandlingInMiddleware.Exceptions.NotImplementedException;
namespace ExceptionHandlingInMiddleware.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
public TestController()
{
}
[HttpGet]
[Route("TestExceptions")]
public IActionResult TestExceptions(int number)
{
CheckTheNumber(number);
return Ok();
}
private void CheckTheNumber(int number)
{
if (number == 1)
{
throw new BadRequestException("Number = 1 is the bad request exception");
}
else if (number == 2)
{
throw new NotFoundException("Number = 2 is the Not found exception");
}
else if (number == 3)
{
throw new NotImplementedException("Number = 3 is the Not implemented exception");
}
else if (number == 4)
{
throw new UnauthorizedException("Number = 4 is the unauthorized exception");
}
}
}
}
As you can see below, the exceptions are working as expected.
When the number is 1
When the number is 2
When the number is 3
When the number is 4
Additionally, you can implement a default response type to inform consumers of the API.
Simply use ProducesResponseType
to define the response type for a controller, as shown below:
using ExceptionHandlingInMiddleware.Exceptions;
using Microsoft.AspNetCore.Mvc;
using NotImplementedException = ExceptionHandlingInMiddleware.Exceptions.NotImplementedException;
namespace ExceptionHandlingInMiddleware.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
public TestController()
{
}
[HttpGet]
[Route("TestExceptions")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public IActionResult TestExceptions(int number)
{
CheckTheNumber(number);
return Ok();
}
private void CheckTheNumber(int number)
{
if (number == 1)
{
throw new BadRequestException("Number = 1 is the bad request exception");
}
else if (number == 2)
{
throw new NotFoundException("Number = 2 is the Not found exception");
}
else if (number == 3)
{
throw new NotImplementedException("Number = 3 is the Not implemented exception");
}
else if (number == 4)
{
throw new UnauthorizedException("Number = 4 is the unauthorized exception");
}
}
}
}
This way, users can understand what kind of response type they might expect from the API.
Note that if you want to pass an object through a custom exception model, you need to change the parameter type from string
to object
. This will allow you to pass your model as the response type.
You can check the code below:
namespace ExceptionHandlingInMiddleware.Exceptions;
public class BadRequestException : Exception
{
public BadRequestException(object message) : base(message.ToString())
{
}
}
In conclusion, implementing global exception handling in ASP.NET Core middleware is a crucial step in enhancing the robustness and reliability of your applications. By creating custom exception classes and integrating them into middleware, you can efficiently manage errors and provide clear, consistent responses to API consumers.
This approach not only simplifies error handling but also improves the overall user experience by ensuring that exceptions are handled gracefully and appropriately. As demonstrated, the process is straightforward and can be tailored to meet specific application needs, allowing for scalable and maintainable error management solutions.
You can refer to the code here.
Subscribe to my newsletter
Read articles from Fırat TONAK directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
