Catching Unhandled Exceptions in ASP.NET
September 19, 2011 Leave a comment
There are various methods that can be used to catch unhandled exceptions in an ASP.NET application. The appropriate method to use depends on the nature of the exception being thrown. This post walks through several examples to demonstrate several different types of “unhandled” exceptions and how to catch them.
This investigation into unhandled exceptions was initiated by a stack overflow exception being thrown in a production application. That type of error proved to be the most challenging “unhandled exception” to handle, not least because of some incomplete or unclear documentation.
The following examples were tested in an ASP.NET WebForms (yes, boring old WebForms) application compiled with ASP.NET 4.0 and hosted with IIS 7.5 on Windows 7.
One type of “unhandled exception” is an exception thrown by a section of code that is not wrapped in a try-catch block. These types of exceptions can be caught by adding an exception handler to the Application_Error event in the global.asax file of a web application.
Most ASP.NET developers are familiar with the Application_Error event. The following example shows an implementation of this event handler that catches and logs unhandled exceptions. Notice that the InnerException of the Exception is what is actually logged. This is because the original exception is wrapped in an HttpUnhandledException by the time it is caught by the Application_Error event.
A page with a single button can be used to test the Application_Error error handler. The code for the button click event is shown here.
Another type of unhandled exception is an error (again not wrapped in a try-catch block) that occurs outside the normal request processing context of the ASP.NET runtime. An example is an error that occurs on another thread. An HttpModule that registers an event handler for the UnhandledException event of the current AppDomain can be used to catch such exceptions.
Http Modules are assemblies that are called on every request. In that respect they are similar to ISAPI filters. Unlike ISAPI filters, they are written in managed code and are integrated with the ASP.NET application life cycle. ASP.NET itself uses modules to implement features such as forms authentication and caching. In regards to handling exceptions, the most important feature of http modules is that can consume application events.
The following is the complete code of a class that implements the IHttpModule interface. It includes an event handler for UnhandledException events.
To use the HttpModule within a web application, compile it and register the assembly in the web.config file, as shown here.
Testing this error handler is a bit more difficult, because the test needs to show that exceptions that bypass the Application_Error event handler are caught by the HttpModule. An error needs to be thrown that is not caught by the “normal” ASP.NET error pipeline (for example, the Application_Error event).
Again start with a single button on a web page. The click event of the button needs to spawn a thread that throws an exception which is not wrapped in a try-catch block.. Here is the code for the click event.
Because the exception happens on a separate thread, the Application_Error event does not catch it. However, the HttpModule does.
Note that such an HttpModule exception handler will also catch any exceptions that an Application_Error event handler in global.asax will catch. So, an HttpModule exception handler can be used in tandem with an Application_Error event handler , or in place of the Application_Error event.
The final type of unhandled exception to examine is an exception that corrupts the state of the application. Probably the best-known example of this is a stack overflow. Because they require special handling, it might seem that exceptions like a StackOverflowException are simply unhandled exceptions that occur outside the normal request processing context of ASP.NET, just as the error in the previous example. In fact, exceptions that corrupt the state of the application are a different class of exception entirely, and by definition cannot be caught.
This is true despite conflicting documentation that suggests that http modules can catch such errors, or that the legacyUnhandledExceptionPolicy setting in the aspnet.config file (located in the framework folder) can be modified to allow ASP.NET to handle such exceptions in a legacy manner (i.e. like ASP.NET 1.0 and 1.1).
Furthermore, some documentation suggests that stack overflow errors can be caught if the block of code throwing the error is decorated with the System.Security.SecurityCritical and System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions attributes. (This, of course, assumes that you know the block of code throwing the error.)
The following is the codebehind for a page that generates a stack overflow error by calling a recursive function that never exits. It illustrates the use of the SecurityCritical and HandleProcessCorruptedStateExceptions attributes that are supposed to allow corrupted state exceptions, including stack overflows, to be caught. The attributes have no effect; the exceptions are not caught by the try-catch block.
In addition, if this page is added to an application that implements the previously discussed Application_Error and HttpModule event handlers, the stack overflow error is not caught. Even changing the legacyUnhandledExceptionPolicy setting in the aspnet.config file has no effect. The stack overflow exception is not caught by any of the error handlers. It seems that all of the documentation that suggests various methods for capturing stack overflow exceptions is incorrect or misleading.
It appears that there is NO WAY to catch and log a stack overflow error. So, how can a stack overflow exception be “handled”?
The answer is to use the Debug Diagnostic Tool from Microsoft (the latest version at the time of this writing is 1.2). This tool includes a debugger service that can capture a dump file when a stack overflow occurs. That file can then be analyzed to find the code that is causing the stack overflow.
Complete configuration and usage details for the Debug Diagnostic Tool are outside the scope of this post. In brief, the steps to follow to capture a stack trace when a stack overflow exception occurs are:
- Install the Debug Diagnostic Tool
- Create a Rule to capture Stack Overflow exceptions and perform a Log Stack Trace action.
- Run the web application.
- Run the Debug Diagnostic Tool.
- Cause the exception to occur.
For more detailed information, see the documentation of the tool here.
When a stack overflow exception occurs, the Debug Diagnostic Tool will capture a stack trace and write it to a log file. An example of the log contents can be seen here (with the function call that is producing the stack overflow highlighted):
The log shows a stack trace which positively identifies the part of the code that is throwing the error (the StackOverflowWebApp.StackOverflow.Overflow(Boolean) method).
Note that the Debug Diagnostic Tool service is set to start automatically. This may not be desirable, especially if the tool is only needed briefly to debug a particular error. Also, the tool seems to affect the performance of the web site being debugged. Use this tool carefully, especially if it must be pointed at a production web site.
In summary, unhandled exceptions in an ASP.NET application can be caught with an Application_Error event handler in the global.asax, or by creating a HttpModule to catch the AppDomain.UnhandledException event. An HttpModule is required to catch unhandled exceptions that occur outside the normal processing of requests by the ASP.NET runtime. For errors that corrupt the state of the application, such as stack overflow exceptions, use the Debug Diagnostic Tool to capture a stack trace at the time of the error.
The complete source code for an application that includes all of the examples shown here is available for download. Please note that the web site should be compiled and hosted under IIS to ensure that the the error handlers will behave properly. Running the application in debugging mode from within Visual Studio produces different results than you will see in a production environment. Visual Studio tries to help handle the errors, but that prevents some of the intended event handlers from working as expected, and does not allow for a complete understanding of how the various error handlers work outside the development environment.