ASP.NET Core offers a very cool way to avoid all that boring boilerplate exception handling code, just by intercepting all requests. It is called
Middleware and actually, ASP.NET Core is full of built-in middlewares!
What is a middleware?
Microsoft docs describe a middleware as:
Middleware is software that’s assembled into an app pipeline to handle requests and responses. Each component:
- Chooses whether to pass the request to the next component in the pipeline.
- Can perform work before and after the next component in the pipeline is invoked.
But you can also get a very good idea about a middleware by just that following image:
The three blue middlewares above belong in a pipeline, so the black arrow that represents the request is passing from one middleware to the next. Each middleware intercepts the request in its travel towards the last middleware and can execute custom code before or after the request leaves the middleware. By seeing this image we can also have an idea about how to write our exception handling middleware: Rename
Middleware 1 to
ErrorHandlingMiddleware and wrap the
next(); in a
try catch block!
Writing an exception handling middleware
I am not going to get into details about how to write a middleware because you can easily read about it here, and thus, here is an empty useless middleware!
InvokeAsync method does all the job and calls the
_next(); middleware. As theory describes, you can have your code before
_next();, you can have it after and you can even decide if you will ever call
_next(); (short-circuiting the pipeline).
But isn’t that alone
_next(); asking for a
try catch block, or what?
Let’s do it, and handle the exception:
Up until this point we have a middleware that can successfully catch any exception, try to serialize it and return it back to the client. That could be the end of this post, but there are three significant problems!
- 1. All exceptions implement the
ISerializableinterface, but this is only suggests that they should be serializable. You can’t be sure they are!
- 2. Exceptions have inner exceptions!
- 3. Handling all exception types in one try catch block is a bad idea because you can hide unexpected problems. We should only handle the ones that our code is written for.
The accepted answer on Stackoverflow for the question “Are all .NET Exceptions serializable” states:
In order to consider serializability we need to consider both the immediate class and all types which are members of this type (this is a recursive process). The base Exception class has a Data property which is of type IDictionary. This is problematic because it allows you, or anyone else, to add any object into the Exception. If this object is not serializable then serialization of the Exception will fail.
That simply means no! You cannot count on an exception being serializable!
One easy way to forget about serializability problems is to use your own custom ViewModel. Add one to your API and map the Exception properties you want with the properties of your brand new
And here is the mapping (notice the recursion for the inner exceptions):
Middlewares, among other, offer an amazing opportunity to create a global handler for our exceptions and return back a serialized version of that exception. This can be very helpful if we want to pass this information to the client, but by doing so we could potentially expose sensitive information and details about our code. The best approach to avoid that would be to use the
ASPNETCORE_ENVIRONMENT and stop sending details when it is not a development environment. For example: