Exceptions and Exception handling is one important aspect of application development, application monitoring. In this application, let’s try to understand different ways to handle exceptions in an ASP .NET Core Web API application.
Try Catch Blocks
This is the primitive way a C# language feature that allows to wrap the application’s functional logic in
try block and then the exception raised from the functional logic can be caught in the immediate next
catch block ( or multiple catch blocks), provided exception type matches.
catch block, we can specify the type of exception that we want to handle. We can use a single
catch block to
catch any exception (all exceptions have base type Exception) – provided different exception types do not need different treatments. Otherwise multiple
catch blocks can be specified.
catch blocks are used, then the exception types used in the
catch block should be ordered in such a way that
catch blocks for more specific exception type should appear before
catch blocks for more general types to ensure that right
catch block is matched.
Below is example code showing an example of
try block followed by multiple
While this way of handling exception is very simple to understand, it might not be first choice in most of the cases. Firstly, this way of exception handling might violate DRY principle. Secondly, if due to some mistake, some error is not handled properly, there might be chances of leaking exception details to the caller – which might not be desired in may real world applications.
Exception Handling Filters
If you have come from .NET Framework’s ASP .NET MVC background, then you might be aware of the concepts of filters. Filters can be used to decorate controllers or actions in an MVC or Web API application.
In .NET Core, technically, we can create exception filters to handle all the exceptions at one single place. This would ensure that application adheres to DRY principal. Once a filter is created, it can be applied to as many controllers or action as the application needs. Also, as this is the single place to handle exception, appropriate code can be added to avoid leaking stack traces to callers.
.NET core provides an abstract base class ExceptionFilterAttribute, which can be extended further in case you decide to use filters to handle exceptions. The
OnException method from this class can be overridden to add application specific logic.
Then, this attribute can be applied to all controllers via startup or it can be placed on individual controllers and/or actions.
Below sample code shows how to use filter for exception handling.
Exception Handling Middleware
Although an extended ExceptionFilterAttribute might seem to be helpful in solving original issues mentioned in first approach(i.e. try-catch blocks), .NET documentation recommends newer way. And it is to use exception handling middleware UseExceptionHandler in .NET’s request processing pipeline.
UseExceptionMiddleware takes a URL as parameter, where a route can be specified. This route would be invoked to handle the error and return appropriate response to the caller.
Below example shows an example.
Why choose middleware over filters ?
The exception handling middleware is invoked for all the controllers in an application. Invoking middleware happens way before deciding a controller and an action which can handle the incoming request.
On the other hand, filters invoked after request has gone through routing middleware. So, the controller and the action to be executed is known.
In my opinion, important reason to use middleware over filters is simplicity. For middleware, we just need to create a controller with some route which can be used with middleware.
If endpoint specific exception handling is not required (which is mostly the case), then exception handling middleware can cater to all the needs of application.
But if you want endpoint specific (either controller specific or action specific) logic for exception handling, then maybe filter can be the best approach.
Environment Specific Logic
Also, another advantage of using middleware is environment specific handling of exceptions can be easily added. For example,
ErrorController can have multiple routes defined, one for each environment. Then Startup can have conditional logic to set appropriate route in middleware depending on current environment.
I hope you find this information useful. Let me know your thoughts.