In ASP.NET Core you can hook up a route to be your global exception handler. Simply add the ExceptionHandler middleware to your Startup.cs Configure method:
In your HomeController’s Error action, add this code to get the exception that was raised, as well as the route that caused the exception, and then you can do something with it (log to a database, send an email/text/fax/carrier pidgeon).
You can checkout this GitHub commit if you’d prefer to see it that way.
The start of the non-tldr;
No ELMAH in ASP.NET Core… yet
In pre-Core versions of ASP.NET, ELMAH was a popular library for doing Global Exception Handling. When unhandled exceptions occurred, it let you do things like log them to a database, send an e-mail, etc. automagically. Unfortunately, ELMAH is not ported to ASP.NET Core yet, so this led me to look at what other options were out there.
You could use one of the many of the Cloud providers for this like Application Insights, elmah.io (unaffiliated with the ELMAH project) or Exceptionless, but what if your company requires you to stay on-prem? You can use Exception Filters, but those have some limitations, and I found a simpler way (arguably) that I didn’t find a blog post on, so I thought I’d write one up.
Step 1 – Hook up the ExceptionHandler middleware
If you’ve done much with ASP.NET Core, this will look very familiar to you. First you need to hook up the ExceptionHandler middleware by adding this line to the Configure method of your Startup.cs:
What that does is anytime an uncaught exception happens, it will send the request to the route listening on the path you specified. In the example above (and the default in the templates), that’s the Error Action on the Home Controller, obviously you can just change that to whatever you want.
Often, you’ll only want to use the Exception Handler in non-development environments, and instead use the DeveloperExceptionPage in Development, which is the new YSOD that gives you a stack trace and other good information.
A sample Configure method showing this is below:
Step 2 – Use IExceptionHandlerPathFeature to get the exception and the Path
Make sure you have the Microsoft.AspNetCore.Diagnostics NuGet package in your .csproj, or if you have the Microsoft.AspNetCore meta-package, then that contains the Microsoft.AspNetCore.Diagnostics package, so you’re all set.
In the action of the controller you’re listening on (in the above case, the Error action on the Home Controller). Add the following code to your Error action.
Breaking the code down
The code is pretty straight forward, but let’s break it down. When an exception occurs, an IExceptionHandlerPathFeature is set. This contains two properties: 1) the Path of the route at which the exception occurred (such as /Home/Index), and 2) the Exception that occurred under the Error property. After that, you can do whatever you want with this information such as log it to a database, send an e-mail or trigger a carrier pidgeon (I believe the CarrierPidgeon API is coming back in .NET Standard 2.0).
If you want to see the code that makes IExceptionHandlerPathFeature work, you can view go to GitHub here and see the code where they set the Path in the ExceptionHandler middleware that we rigged up in our Startup class.
Make sure you get the right Feature
Be careful to make sure you get an IExceptionHandlerPathFeature with the Path in the name. There is also a IExceptionHandlerFeature without Path in the name that does not have the Path of where the exception occurred. Obviously, it’s pretty useful to know the source route of where the exception happened.
Make sure you catch any Exceptions in your Exception Handler Action
Also be careful and make sure to catch any exceptions in your Exception Handler Action. Causing a second exception in your exception handler action will cause the entire middleware to stop and you’ll end up with a blank page and a 500 status code. A scenario where this could happen is if you’re logging the exception to a database, but your database is down causing an exception.
Microsoft provides a package called ELM (Error Logging Middleware) that has some similar features to what ELMAH did. One of the main features ELM has is it allows you to see events on a web page that aggregates those events. You can read more about that here. However, there doesn’t seem to be any intent to extend ELM to do things like report to a database or send e-mails, based on this response from Eilon of the ASP.NET team here, and it is non-persistent through app restarts. In my scenarios, we don’t have a ton of use for this, as we prefer to be able to look and query for logs in a database, but I thought I’d mention it in case it is useful for your scenarios.
Additionally – AppDomains Coming Back in .NET Standard 2.0
If you’re using .NET Core (and not full framework), AppDomains are coming back in .NET Standard 2.0 to help with global exception handling, as described by Damian Edwards in the ASP.NET Community Standup here: https://youtu.be/_3y0QsXj2e4?t=31m17s
Don’t like using IExceptionHandlerPathFeature in your Controller action?
That’s ok. There’s another way to achieve this same thing using raw middleware, even bypassing IExceptionHandlerPathFeature completely, but I’ll save that for a followup blog post, as this is approaching 1000 words.
Thanks for reading!