Error handling in ASP NET Core WEB APIs
Error handling is an essential thing in every software product. In the case of APIs, missing error handling or giving correct information and details about the error can become a pain point for developers using your API. In the case of applications with a front-end, it can be worst because giving misleading information to a user can cause frustration and, eventually, cost you users.
In this case, I will focus on ASP NET Core and the use of custom exceptions and global error handling. You can find the code presented in this post in my GitHub repository. This API exposes three endpoints related to currency exchange rates. These are the features implemented in the API:
- List the currency exchange rates for a given currency (GBP, EUR, USD, PEN).
- Add new currency exchange rates
- Update an existing currency exchange rate
Common problems when handling errors in NET Core apps
I will get started by providing a couple of code examples that I have found a few times in different projects and what problems they represent. After that, we will look at a better approach to handling errors.
First approach: Adding try-catch and hiding errors.
Can you spot the problem with this code?
- First of all, You are hiding errors from the consumer. In this API, the method in the class we see is done from a controller that exposes a REST endpoint. You are giving the user a code 200 even in the case of failure. In most cases, we will want to return an HTTP code that reflects what happened, for example, in case of a server error in the range of 500 - 599.
- The second issue is that following this approach you would have to add the try-catch statement around every single piece of code you want to catch and log.
Second approach: Adding try-catch and rethrow.
Now we have solved the issue of "hiding" the errors because we are rethrowing the exception, but we would still need to add the try/catch statement around the code we want to use logs.
Using custom exceptions and a custom middleware
A better approach to this problem is adding custom exceptions to control errors related to the business logic of our application and a global error handler class that can be wired up in the app as a middleware. This approach will allow us to have one central class to handle the errors and better responses to the users or consumers of our software.
Adding a custom exception
Our first step will be creating a custom exceptions for errors concercened with business logic of the app, in this case that will be thrown when a currency which is not supported Remember that custom exceptions should derive from the Exception class and not from ApplicationException.
You can find more information about exceptions in DOT NET here:
Adding a middleware to handle errors
NET Core framework provides the ability to add custom middleware. A middleware is code that is added to the app pipeline to handle requests and responses. To add a middleware we can create a class that implements the IMiddleware interface. Another option is just creating a class that does not implement any interface. However, you still need a public method with the following signature: Task InvokeAsync(HttpContext context, RequestDelegate next).
Wire up error handler middleware
Finally, we need to wire up the middleware class into the pipeline of our application.
We can do so in the Program class by calling the UseMiddleware method available in the app object: app.UseMiddleware
This is all we need to implement global error handling in a NET Core API. As mentioned at the beginning of the article, the complete example can be found on GitHub.
Custom exceptions and middleware classes are not the only techniques to handle errors. There are other techniques that you can use, such as Model Validation. Model Validation is a good approach for customer-facing applications or APIs where we need to validate properties in an object and give the user details of every rule that failed. You may have already noticed that when using exceptions, you will not have details of all failures because we stop the flow as soon as we find an error by throwing an exception.