Elevating Code Resilience: A Deep Dive into Exception Handling in Middleware with ASP.NET Core 6

Firat Tonak
6 min readMar 29, 2023

--

As we delve into this article, we will explore how to handle exceptions globally and implement them.

We will utilize some common exceptions and throw them when encountering any errors. Subsequently, we will catch these exceptions 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 have, you should have a structure as below

Let’s create a folder called ‘Exceptions’ where we will develop our custom exception classes. These classes will inherit from the Exception class.

The first one will be the ‘BadRequestException’, which will inherit from the Exception class under the System namespace.

namespace ExceptionHandlingInMiddleware.Exceptions;

public class BadRequestException : Exception
{
public BadRequestException(string message) : base(message)
{

}
}

The second one will be the ‘NotFoundException’.

namespace ExceptionHandlingInMiddleware.Exceptions;

public class NotFoundException : Exception
{
public NotFoundException(string message):base(message)
{

}
}

The third one will be the ‘NotImplementedException’.

namespace ExceptionHandlingInMiddleware.Exceptions;

public class NotImplementedException : Exception
{
public NotImplementedException(string message) : base(message)
{

}
}

The last one will be the ‘UnauthorizedException’.

namespace ExceptionHandlingInMiddleware.Exceptions;

public class UnauthorizedException:Exception
{
public UnauthorizedException(string message):base(message)
{

}
}

Now we have finished creating custom exception classes. However, you can create more custom exception classes if needed.

You should have a structure as follows after creating all classes

Now, we need to integrate our exceptions into middleware. This is why we will create a folder called ‘Configurations’ where we will implement our middleware and consume it in the Program.cs file.

After creating the folder, we need to create a middleware class called ‘ExceptionMiddlewareConfiguration’ and begin implementing it.

First, we will add the request delegate to catch 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
}
}


}

Then, we need to check what kind of exception will be 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 you can see 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.

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, we all 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 an API and test the exceptions.

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 have 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");
}
}

}
}

Then, users can understand what kind of response type they might expect.

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, and then you will be able 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())
{

}
}

I’ve tried to explain how to handle exceptions globally, and as you can see, it is a relatively straightforward process. Additionally, if desired, you can extend your exception classes and throw them according to your specific needs.

You can refer to the code here

--

--

Firat Tonak

Seasoned Senior Full-Stack .NET Developer sharing hands-on experience and cutting-edge technologies in comprehensive Full-Stack development. #TechInsights