We will check on how to handle exceptions globally in this article and implement it.
We will use some common exceptions and throw them when we run into any error. Then, we will catch it using a try-catch block in middleware.
You can find more details about try-catch block here
I assume that you have already created a Web API project to implement it. If you do, you should have a structure as below
Let’s create a folder called Exceptions where we will create our custom exception classes. Our exception classes will be inherited from the Exception class.
The first one will be the bad request exception which will be an inherited Exception class that is under the system namespace.
namespace ExceptionHandlingInMiddleware.Exceptions;
public class BadRequestException : Exception
{
public BadRequestException(string message) : base(message)
{
}
}
The second one will be the not found exception.
namespace ExceptionHandlingInMiddleware.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException(string message):base(message)
{
}
}
The third one will be not implemented exception
namespace ExceptionHandlingInMiddleware.Exceptions;
public class NotImplementedException : Exception
{
public NotImplementedException(string message) : base(message)
{
}
}
The last one will be the unauthorized exception.
namespace ExceptionHandlingInMiddleware.Exceptions;
public class UnauthorizedException:Exception
{
public UnauthorizedException(string message):base(message)
{
}
}
Now we are done creating custom exception classes, however, you can create more custom exceptions class if you need.
You should have a structure as below after creating all classes.
Now, we need to tie up our exceptions to middleware which is why we will create a folder called Configurations where we will implement our middleware and consume it in Program.cs file.
After creating the folder, we need to create a middleware class called ExceptionMiddlewareConfiguration, then start implementing it.
First, we will add the request delegate to catch the request pipeline. Here are more details about the request pipeline.
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
}
}
}
Then, we need to check what kind of exception that will be passed and determine which status code we need 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 httpcodes will be determined based on exceptions
HttpStatusCode statusCode;
string message = "Unexpected error";
// we need to find out the type of the exception
var excpType = ex.GetType();
// let's check what kind of exceptions 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 you can see above, we are able to check the type of exception and return the code.
Now we need to inject our middleware class into the Program.cs file.
Let’s create a static class called ApplicationBuilderConfiguration under the Configuration folder.
namespace ExceptionHandlingInMiddleware.Configurations;
public static class ApplicationBuilderConfiguration
{
public static IApplicationBuilder ErrorHandler(this IApplicationBuilder applicationBuilder) => applicationBuilder.UseMiddleware<ExceptionMiddlewareConfiguration>();
}
Then only what we need to do calling the ApplicationBuilderConfiguration static file 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, time to test our exception-handling implementation. Let’s create an API and test the exceptions.
Create a controller called TestController and implement 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");
}
}
}
}
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
Also, you can implement a default response type to inform people who will consume the API.
Only we need to use ProducesResponseType and you will have a controller as 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");
}
}
}
}
Then, people can understand what kind of response type they might have.
Note that if you want to pass an object through a custom exception model, you need to do changing the parameter type from string to object and you will be able to pass your model as response type.
you can check on the below code
namespace ExceptionHandlingInMiddleware.Exceptions;
public class BadRequestException : Exception
{
public BadRequestException(object message) : base(message.ToString())
{
}
}
I tried to explain how to handle exceptions globally and as you can see it is a pretty easy process. Also, if you want, you can extend your exception classes and throw them as per your needs.
You can reach out to code here