top of page

Global Exception Handling in .NET 6

Exception handling is one of the important tasks in the application development cycle. Today, I am planning to tell you the effective way to handle the exceptions in the .Net 6. In that case, I’ll discuss,

  1. Exception Handling with Try-Catch Block

  2. Global Exception Handling with Custom MiddleWare


1. Exception Handling with Try-Catch Block

Try-Catch block is a basic approach to handle the exception, and let’s try it with the code example. I’ll use the ASP.NET Core Web API project based on the .Net 6.

using ExceptionHandling.Services;
using Microsoft.AspNetCore.Mvc;

namespace ExceptionHandling.Controllers;

public class UserController : Controller
{
    private readonly IUserService_userService;
    private readonly ILogger<UserController> _logger;
    
    public UserController
            (IUserService userService, ILogger<UserController> logger)    
    {
        _userService=userService;
        _logger=logger;    
    }    
    
    [HttpGet]
    public IActionResult GetUsers()    
    {
        try        
        {
            _logger.LogInformation("Getting Users details");
            
            var result =_userService.GetUsers();
            if (result == null)
                throw new ApplicationException("Getting Errors while 
                fetching users details");
            return Ok(result);        
        }
        catch (Exception e)        
        {
            _logger.LogError(e.Message);
            return BadRequest("Internal Server error");        
        }    
    }
}

Now, we have to check this one.

Response without exceptions


You could get the success response without exception as the above figure.

Now you have to throw the exceptions to check the try-catch block.


Response with exception


Log Error Messages


Here is a try-catch block example, and it will work fine. This approach is really good for beginner-level development work, and this is fundamental that every developer needs to know. But there is a downside of this approach when dealing with large projects.

Imagine that you have many controllers and actions in your project and then you need to use try-catch for every action in the controllers. some times you have to use try-catch in the services. In that case, it increases the lines of code your project and it is not good. There is a good approach to handling the problem mentioned above.



2. Global Exception Handling with Custom Middleware

This is a good and effective approach to handling the exception global level. We could be caught all unhandled exceptions using this exception handler. The benefit of using this approach is we could be caught all exceptions in one place and don’t need to use try-catch in every action to catch errors. In that case, we need to create custom middleware to handle the exceptions.

Let’s try the code example.

Let’s create the new Middlewares folder in the root folder and create a class called ExceptionHandlingMiddleware inside the Middlewares folder you created.

using System.Net;
using System.Text.Json;
using ExceptionHandling.Models.Responses;

namespace ExceptionHandling.Middlewares;

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;
    public ExceptionHandlingMiddleware
        (
            RequestDelegate next, 
            ILogger<ExceptionHandlingMiddleware> logger)    
    {
        _next = next;
        _logger = logger;    
    }
    
    public async Task InvokeAsync(HttpContext httpContext)    
    {
        try        
        {
            await _next(httpContext);        
        }
        catch (Exception ex)        
        {
            await HandleExceptionAsync(httpContext, ex);        
        }    
    }
    private asyncTask HandleExceptionAsync
                            (HttpContext context, Exception exception)    
    {
        context.Response.ContentType="application/json";
        var response = context.Response;
        
        var errorResponse = new ErrorResponse        
        {
            Success=false        
        };
        switch (exception)        
        {
            case ApplicationExceptionex:
                if (ex.Message.Contains("Invalid token"))                
                {
                    response.StatusCode = (int) HttpStatusCode.Forbidden;
                    errorResponse.Message=ex.Message;
                    break;                
                }
                response.StatusCode = (int) HttpStatusCode.BadRequest;
                errorResponse.Message=ex.Message;
                break;
            case KeyNotFoundExceptionex:
                response.StatusCode = (int) HttpStatusCode.NotFound;
                errorResponse.Message=ex.Message;
                break;
            default:
                response.StatusCode = (int) 
                                    HttpStatusCode.InternalServerError;
                errorResponse.Message="Internal Server errors. Check 
                Logs!";
                break;        
        }
        _logger.LogError(exception.Message);
        var result = JsonSerializer.Serialize(errorResponse);
        await context.Response.WriteAsync(result);    
    }
}

Here is the custom middleware that creates to handle the exceptions. In that case, first, we need to register ILoggerand RequestDelegateservices using the dependency injection. The RequestDeleagate type’s _nextparameter is a function delegate that may handle our HTTP requests. Also, the request delegate is passed to middleware. This is either processed by the middleware or passed on to the next middleware in the chain.

If the request is unsuccessful, there may be an exception, and the HandleExceptionAsync method will execute to catch the exception according to the exception type. In that case, you could switch statements to identify the exception type, and then we could use the proper status code according to the exception as an above code example. And Also, we don’t need to pass the exception messages to the client-side of the project. In that case, we could pass the custom message and then use ILogger to log the exception message as an error. Then we could identify the exception message by checking the logs.

Let’s add our custom middleware in the program.cs.

Note: .Net 6 hasn’t used startupclass as in previous versions. In that case, You have to add our services and configurations to the program.csfile. If you use the previous version, you could add the custom middleware inside the Configure method in the startup class.

app.UseMiddleware<ExceptionHandlingMiddleware>();

Now we have to modify the UserController as below code example.

using ExceptionHandling.Services;
using Microsoft.AspNetCore.Mvc;

namespace ExceptionHandling.Controllers;

[ApiController]
[Route("api/[controller]")]
public class UserController : Controller
{    
    private readonly IUserService _userService;    
    private readonly ILogger<UserController> _logger;    
    
    public UserController(
           IUserService userService, ILogger<UserController> logger)    
    {        
        _userService = userService;        
        _logger = logger;    
    }        
    
    [HttpGet]    
    public IActionResult GetUsers()    
    {                
        _logger.LogInformation("Getting Users details");        
        
        var result = _userService.GetUsers();        
        if (result.Count == 0)            
            throw new KeyNotFoundException("Users not found. Try again");        
        return Ok(result);            
    }
}

We have to remove the try-catch block inside the action of UserController. Because we will handle this approach through the custom middleware, and we don’t need the try-catch here. We only need to throw the exception according to the use case.

Now we have to check it. We could succeed response as we get the first example above.

Response with the exception



Here is the exception response we get when using the custom middleware. Here, we use the model called ErrorResponse to get the custom error messages.

Log Error Messages


Note: I didn’t mention the user service other services in this demonstration. But you could refer to the Github repository for this demonstration, including the entire project.



Summary

Finally, we implement the custom middleware to handle the exceptions at the global level in the ASP.NET Core Web API project based on the .NET 6. So, this approach is really good when dealing with large projects. Because then we don’t need to use try-catch in every action in the controllers. It also increases the code readability and helps maintain the clean and reusable exception handling approach entire project.



Source: Medium - Yohan Malshika


The Tech Platform

bottom of page