Exception handling is a critical aspect of an ASP.NET application. While we often use try-catch blocks for this, we can streamline the process by implementing a global error handling mechanism.
.NET 8 introduce a new feature called IExceptionHandler . This interface is under Microsoft.AspNetCore.Diagnostics namespace.
What is IExceptionHandler ?
- Centralized Exception Handling: We can handle all known exception in one place.
- Custom Logics: We can define how to handle different types of exceptions
- Control Processing: Handlers can return true to stop further processing or false to let other handlers take over.
Let’s build a simple .NET application with an EmployeeController
and an
EmployeeModel
to define a few basic properties.
public class EmployeeModel
{
public int Id { get; set; }
public string Name { get; set; }
public string PhoneNo { get; set; }
}
EmployeeController
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace GlobalExceptionHandling.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly List<EmployeeModel> employees;
public EmployeeController()
{
employees = new List<EmployeeModel>();
employees.Add(new EmployeeModel { Id = 1001, Name = "Nazib Mahfuz", PhoneNo = "01777127618" });
employees.Add(new EmployeeModel { Id = 1002, Name = "Kazi Malik", PhoneNo = "01777127618" });
employees.Add(new EmployeeModel { Id = 1003, Name = "Ali Hasan Mito", PhoneNo = "01777127618" });
employees.Add(new EmployeeModel { Id = 1004, Name = "Tahmid Hossain", PhoneNo = "01777127618" });
employees.Add(new EmployeeModel { Id = 1005, Name = "Maksudul Islam Rahat", PhoneNo = "01777127618" });
employees.Add(new EmployeeModel { Id = 1006, Name = "Millat Kanchan", PhoneNo = "01777127618" });
}
[HttpGet]
[Route("GetAllEmployee")]
public async Task<IActionResult> GetAllEmployees()
{
try
{
int totalEmployee = employees.Count();
int perPage = totalEmployee / 0; // Force create exception
return Ok(employees);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
[HttpGet]
[Route("GetEmployeeByID")]
public async Task<IActionResult> GetEmployeeByID()
{
try
{
var employee = employees.Find(x => x.Id == 1009);
return Ok(employee);
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}
}
Here, we create an EmployeeList
and populate it with some dummy data. For simplicity, we define two API endpoints: GetAllEmployee
and GetEmployeeByID
. To handle exceptions, we use a try-catch block in each case. To handle the exception in one place we create a class called AppExceptionHandler
. This class must be inherit by IExceptionHandler
interfaces. Here is the implementation
using Microsoft.AspNetCore.Diagnostics;
namespace GlobalExceptionHandling
{
public class AppExceptionHandler : IExceptionHandler
{
public ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
return ValueTask.FromResult(true);
}
}
}
We need to implement the TryHandleAsync
method, which takes three parameters:
- HttpContext: This represents the current HTTP request and response context. It provides access to request details such as headers, route data, and user information. We can use this parameter to retrieve data and modify our exception handling logic accordingly.
- Exception: This parameter contains details about the exception, including the error message, whenever an error occurs.
- CancellationToken: This special parameter indicates whether the async operation should be canceled. It’s used to handle cancellation requests from the user or system during the execution of an async task. --> If a client disconnects or the server shuts down, the cancellation token notifies the handler to stop further processing. --> By halting unnecessary operations, it helps conserve system resources such as memory and CPU.
Registering IExceptionHandler
Next, we need to register our global exception handler class in the program to ensure that any exceptions are caught and handled automatically.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddExceptionHandler<AppExceptionHandler>();
app.UseExceptionHandler( _ => { });
We register our AppExceptionHandler class using the line builder.Services.AddExceptionHandler(). Additionally, we need to add the middleware UseExceptionHandler() and pass a parameter.
In this case, the _ symbol is a convention in C# to indicate that the parameter is unused, meaning we don't need to use the IApplicationBuilder object in our custom handler logic.
The { } part represents the body of the lambda expression, where you can write the logic to handle exceptions globally.
Now, we can create another class to display global exceptions in a formatted way. Here’s the class.
public class ExceptionResponse
{
public int StatusCode { get; set; }
public string Title { get; set; }
public string ExceptionMessage { get; set; }
public DateTime ExceptionDateTime { get; set; }
public string StackTrace { get; set; }
}
Update TryHandleAsync
methods
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var exceptionResponse = new ExceptionResponse()
{
StatusCode = StatusCodes.Status500InternalServerError,
Title = "Unexpected Error Occured",
ExceptionMessage = exception.Message,
ExceptionDateTime = DateTime.UtcNow,
StackTrace = exception.StackTrace ?? "No Stack Trace Found"
};
var jsonResponse = JsonSerializer.Serialize(exceptionResponse);
httpContext.Response.StatusCode = exceptionResponse.StatusCode;
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(jsonResponse);
return true;
}
We can also retrieve the stack trace details. This means we no longer need to write a try-catch block in each method. We can refine our methods as follows:
EmployeeController.cs
[HttpGet]
[Route("GetAllEmployee")]
public async Task<IActionResult> GetAllEmployees()
{
int totalEmployee = employees.Count();
int perPage = totalEmployee / 0;
return Ok(employees);
}
[HttpGet]
[Route("GetEmployeeByID")]
public async Task<IActionResult> GetEmployeeByID()
{
var employee = employees.Find(x => x.Id == 1009);
return Ok(employee);
}
Conclusion
The IExceptionHandler interface in .NET 8 simplifies centralized exception management. By implementing and registering custom exception handlers, you can ensure consistent and maintainable error handling across your application. This enhances the overall quality of your code and makes your application more robust.
Top comments (0)