Last year my organization decided to have a standard specification across all our internal REST APIs. As a part of that exercise, we decided to adopt JSON:API spec for our APIs.
I had also written a post on JSON:API for .NET Developers. I intended to write a series on JSON:API. However, my company’s plan to adopt JSON:API spec dropped, and those posts remain in my drafts.
But the entire exercise was not a complete waste of time. JSON:API taught me a few lessons. And one of them was handling the different HTTP error status codes in a single consistent way.
Challenges with Error Handling in REST APIs
REST APIs have a very loose interpretation. There is no single “standard”. And this also shows in the error handling code for ASP.NET Core. There are primarily two issues I see with the error handling implementation in ASP.NET Core:
- It is common to see that popular HTTP error status codes such as
400 Bad Request,
404 Not found,
500 Internal Server Error, and so on return different HTTP Response format. In fact, even for a single status code, the response could be plain text or JSON. This makes the error parsing at the client-side very difficult since the response could vary based on status code and sometimes even for the same status code.
- In addition to this, the error handling logic is scattered across all the Controllers. That makes it tricky to change a response object since we would need to update every controller.
Below pseudo-code is an example controller with the inconsistent response for different error codes.
Warning: The code-snippet is for demonstration purposes only.
Consistent Error Handling
But before I go further, a quick disclaimer.
Disclaimer: It is a highly opinionated approach that has worked well for my team. But please exercise caution and see if it fits your purpose before adopting.
Return Error Response object
We first start by creating an
Error response object that we will return for any error status code from our application.
The first part of our problem can be easily solved using an
ErrorResponse object for every HTTP error response. However, we still need to handle errors everywhere in the code.
Create Exception Class
To solve the next part of our problem, we start by creating an
Exception class for every error status code.
As you can see in the above code, we have a separate exception class for every error status code that we expect our application to return.
We can also reuse existing Exception classes if it fits our purpose. For example, if we are using FluentValidation for model validation, then we can leverage ValidationException class for
Throwing relevant exception from code
Next, instead of returning HTTP error status code from the
Controller, we throw a relevant exception.
If you are following Clean Architecture or Domain-Driven Design (DDD) in your project, then the Controllers are usually thin. With DDD, the business logic stays within the Domain.
CommandHandler act as an interface between the
Repository. In addition to this, the cross-cutting concerns such a validation, authorization etc., are handled through
ActionFilter, keeping the Controllers lean.
This allows us to throw a relevant exception from different parts of our code. The below pseudo-code demonstrates how we can keep the error handling outside the Controllers by throwing the Exceptions.
Create Application Exception Middleware and ExceptionHandler
Next, we create
ExceptionHandler classes to handle all the exceptions that are thrown from the different parts of the code. The
ExceptionHandler is one single place where we handle all our HTTP error status, and we return the error response to the client through
Last but not the least, we need to register
In this post, I have presented an opinionated way to achieve consistent error handling for your ASP.NET Core Rest APIs. I hope you enjoy reading this post and it was helpful in some way.
Please share your feedback in the comment section below. 🙂