DEV Community

Sanket Naik
Sanket Naik

Posted on

Efficient Way of Writing Controlled Global Exceptions in .NET Core🕵

Introduction 📢
Hey folks!!!
How are you all!!! Here I am with the new blog “Efficient Way of Writing Controlled Global Exceptions in.NET Core”.

In this exciting blog titled “Efficient Way of Writing Controlled Global Exceptions in.NET Core” I’ll share valuable insights on effectively managing exceptions in a controlled manner, resulting in a positive impact on your application. This article offers unique perspectives that you won’t find elsewhere. So, get ready to dive into some interesting learnings!

Make sure to pay attention to the specific use case I’ll discuss later in the article below.

Why you need custom loggings🤔
There are numerous reasons why you may desire these features, but for the sake of this article’s focus and objective, I won’t delve into all of them here. However, I will provide a list of some common reasons:

  1. Customizing logging behavior.
  2. Tracking additional contextual information.
  3. Logging to multiple destinations (e.g., displaying informational logs in the console while storing only failed requests in the LoggingDB).
  4. Improving logging readability for future analysis.
  5. Integrating and customizing behavior for multiple destinations (e.g., storing informational logs in different databases and error logs in separate databases).
  6. Presenting a well-formatted error structure to API users or consumers.

Any other way to write global level logs other than Middleware way?
Yes, you can implement IExceptionFilter. However, it's essential to understand the fundamental difference between IExceptionFilter and CustomExceptionHandlerMiddlewarein terms of their execution within the request pipeline. Perhaps I will write another article that provides a detailed comparison and helps you choose between the two.

For now, the diagram below will give you a general understanding of when each component comes into play:
CustomExceptionMiddleware Vs ExceptionFilter

‘UseCase’ for the approach that I use✍:
I have multiple microservices as a part of my big codebase. These multiple web API services will have almost same code for GlobalExceptionHandlerMiddleware, which basically handles unhandled exceptions at the request level.

However, I need to be consistent in terms of standard HTTP response codes so that I don’t violate DRY principles.

For the most commonly encountered exceptions, the best practices follow the following HTTP codes.

Let's architect this scenario into code 🏹:
Yah! so, the idea is to abstract the most used exceptions along with their http code in a separate shared library.
Below, we have Three exception types in our shared library DomainException,ApplicationExceptionand InfrastructureException.

And the consumers will inherit from below Three base exceptions.
High level idea

Write DomainException base

[Serializable]
public abstract class DomainException: Exception {
    protected DomainException() {}

    protected DomainException(string message)
        : base(message) {}

    protected DomainException(string message, Exception exception)
        : base(message, exception) {}
}
Enter fullscreen mode Exit fullscreen mode

Write ApplicationException base

[Serializable]
public class ApplicationException: Exception {
    protected ApplicationException() {}

    protected ApplicationException(string message)
        : base(message) {}

    protected ApplicationException(string message, Exception exception)
        : base(message, exception) {}
}
Enter fullscreen mode Exit fullscreen mode

Write InfrastructureExceptionbase

    [Serializable]
    public class InfrastructureException: Exception {
        protected InfrastructureException() {}

        protected InfrastructureException(string message)
            : base(message) {}

        protected InfrastructureException(
            string message, Exception exception)
                : base(message, exception) {}
    }
Enter fullscreen mode Exit fullscreen mode

Write Middleware implementation

// GlobalExceptionHandlerMiddleware.cs

public class GlobalExceptionHandlerMiddleware {
    public async Task InvokeAsync(HttpContext context) {
        try {
            await _next(context).ConfigureAwait(false);
        } catch (DomainException ex) {
            await HandleException(context, ex, HttpStatusCode.BadRequest);
        } catch (ApplicationException ex) {
            await HandleException(context, ex, HttpStatusCode.InternalServerError);
        } catch (InfrastructureException ex) {
            await HandleException(context, ex, HttpStatusCode.ServiceUnavailable);
        } catch (ArgumentException ex) {
            await HandleException(context, ex, HttpStatusCode.BadRequest);
        } catch (Exception ex) {
            await HandleException(context, ex, HttpStatusCode.InternalServerError);
        }
    }

    private async Task HandleException(HttpContext httpContext, Exception exception, HttpStatusCode statusCode, bool logRequestBody = true) {
        // Log into Infra/Console/File etc
        WriteResponse(httpContext, statusCode);
    }

    //Modify and return request context
    private async Task WriteResponse(HttpContext httpContext, HttpStatusCode statusCode) {
        httpContext.Response.ContentType = "application/json";
        httpContext.Response.StatusCode = (int) statusCode;

        var responseModel = new {
            ..........
        };
        await httpContext.Response.WriteAsync(
            JsonSerializer.Serialize(responseModel, _seralizeSetting),
            Encoding.UTF8);
    }
}
Enter fullscreen mode Exit fullscreen mode
    //Expose an extension method from your shared library
    public static IApplicationBuilder UseGlobalExceptionHandling(
        this IApplicationBuilder app) {
        app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
        return app;
    }
Enter fullscreen mode Exit fullscreen mode

*Finally, Make sure your code line app.UseGlobalExceptionHandeling() as your first middleware in request pipeline.

Enjoy using Exceptions from consumer code:
This is your Client/Consumer code

Summary📚
I’m excited to share this blog titled “Efficient Way of Writing Controlled Global Exceptions in .NET Core”! In this article, I’ll provide valuable insights on how you can effectively manage exceptions in a controlled manner, ensuring a positive impact on your application.

We saw below points:

  1. We were onto mission on: valuable insights on how you can effectively manage exceptions in a controlled manner.
  2. We saw the ‘why’ part of ‘need of custom logging’.
  3. Then we saw the UseCase of above approach I had written.
  4. We saw the HTTP codes for general types of exceptions.
  5. Then we also went through the implementation idea and the actual hands-on code by making our hands dirty.
  6. Above is just example in my perticular use case. You can have your own/different ExceptionsTypes that most suits in your context.🙌

Top comments (1)

Collapse
 
davidbrown722 profile image
davidbrown722

Thanks for sharing! Efficient exception handling is crucial in any development project. Looking forward to learning more about Forbidden Pants approach in .NET Core.