Introduction
Exceptions are a construct in the .NET Framework that are (ideally) used to indicate an unexpected state in executing code. For example, when working with a database the underlying ADO.NET code that communicates with the database raises an exception if the database is offline or if the database reports an error when executing a query. Similarly, if you attempt to cast user input from one type to another - say from a string to an integer - but the user's input is not valid, an exception will be thrown. You can also raise exceptions from your own code by using the Throw
keyword.
When an exception is thrown it is passed up the call stack. That is, if MethodA
calls MethodB
, and then MethodB
raises an exception, MethodA
is given the opportunity to execute code in response to the exception. Specifically, MethodA
can do one of two things: it can catch the exception (using a Try
... Catch
block) and execute code in response to the exception being throw; or it can ignore the exception and let it percolate up the call stack. If the exception is percolated up the call stack - either by MethodA
not catching the exception or by MethodA
re-throwing the exception - then the exception information will be passed up to the method that called MethodA
. If no method in the call stack handles the exception then it will eventually reach the ASP.NET runtime, which will display the configured error page (the Yellow Screen of Death, by default).
In my experience as a consultant and trainer I have worked with dozens of companies and hundreds of developers and have seen a variety of techniques used for handling exceptions in ASP.NET applications. Some have never used Try
... Catch
blocks; others surrounded the code in every method with one. Some logged exception details while others simply swallowed them. This article presents my views and advice on how best to handle exceptions in an ASP.NET application. Read on to learn more!
The Crib Notes |
---|
My advice for handling exceptions in an ASP.NET application can be boiled down to the following guidelines:
|
First Things First: Create a Custom Error Page
Whenever an unhandled exception occurs, the ASP.NET runtime displays its configured error page. (An unhandled exception is an exception that is not caught and handled by some method in the call stack when the exception is raised. If no method in the call stack catches the exception then it percolates all the way up to the ASP.NET runtime, which then displays the error page.)
The error page displayed by the ASP.NET runtime depends on two factors:
<customErrors>
configuration in Web.config
. ASP.NET's default error page is affectionately referred to as the Yellow Screen of Death. The screen shot on the right shows the default Yellow Screen of Death error page shown when an unhandled exception occurs for a remote visitor. When visiting locally, the Yellow Screen of Death page includes additional information about the exception that was raised.
While the Yellow Screen of Death error page is acceptable in the development environment, displaying such an error page in production to real users smacks of an unprofessional website. Instead, your ASP.NET application should use a custom error page. A custom error page is a user-friendly error page that you create in your project. Unlike the Yellow Screen of Death error page, a custom error page can match the look and feel of your existing website and explain to the user that there was a problem and provide suggestions or steps for the user to take.
Janko Jovanovic's article Exception Handling Best Practices in ASP.NET Web Applications offers advice on the information to display in a custom error page. At minimum, you need to inform the user that something went awry, but as Janko points out:
[In the custom error page] you can provide a user with a meaningful set of messages that will explain:By doing this you are eliminating the confusion in users and allowing them to react properly. [The] image below shows an example of a well designed error screen.
- what happened
- what will be affected
- what the user can do from there
- and any valuable support information
![]()
To use a custom error page first create the error page in your website. Next, go to the Web.config
file and set the defaultRedirect
attribute in the <customErrors>
section to the URL of your custom error page. Finally, make sure that the mode
attribute of the <customErrors>
section is set to either On
or RemoteOnly
. That's all there is to it!
<customErrors mode="On" |
For more information on creating a custom error page and configuring Web.config
, see Gracefully Responding to Unhandled Exceptions - Displaying User-Friendly Error Pages and Displaying a Custom Error Page.
Only Handle Exceptions When...
I've reviewed a number of applications where the developer used Try
... Catch
blocks like they were going out of style. They would surround each and every logical block of code with a Try
... Catch
. When an exception was caught, the developer would then do one of the following:
Try |
Or:
Try |
Try |
Catch
block to return a status code, as in:
Try |
All of these approaches are unpalatable. In fact, it is my opinion that Try
... Catch
blocks should rarely be used. Typically when an exception is raised it indicates an abnormal stoppage of the application. If the database is offline, the data-driven web application is rendered useless. If the user's input is converted from a string to an integer, but that conversion fails, we can't move forward. In the majority of cases it's best to let the exception percolate up to the ASP.NET runtime and have the custom error page displayed.
Try ... Finally a Different Matter Altogether |
---|
Try ... Finally blocks are useful for executing cleanup code regardless of whether an exception occurs or not, and should be used as needed. Keep in mind that you can have a Try ... Finally block without a Catch statement. Moreover, any sort of cleanup logic that needs to occur should always be put in the Finally rather than in the Catch . |
However, there are times when there are two (or more) paths to a happy resolution, and an exception simply indicates that one path has a roadblock and that an alternative path must be tried. Consider an e-Commerce application that automatically sends an e-mail receipt to the customer once their purchase has been completed. What should happen if the SMTP server used to send the e-mail is offline? If there are two (or more) known SMTP servers, the code could try to send from one of the alternative SMTP servers should the primary one be unavailable. This logic could be handled via Try
... Catch
blocks in the following manner:
This workflow could be implemented with the following pseudocode:
Try |
The above scenario is a good use of Try
... Catch
blocks because we are trying to recover from the exception. If all you are doing is re-throwing the exception (and nothing else) then the Try
... Catch
block is superfluous. But if you can possibly recover from the exception then it makes sense to have a Try
... Catch
block.
Taking the above example a step further, there are scenarios where an exception does not mean that the application should terminate. If a receipt e-mail cannot be sent to the user we should still process the order. To extend the steps outlined above a bit further we would have:
This updated workflow could be implemented with the following pseudocode:
Try |
Finally, Try
... Catch
blocks are useful in situations where you need to let the exception percolate up the call stack, but before doing so you want to add additional information about the exception. This is accomplished by catching the exception and then throwing a new exception with the original exception as an inner exception.
To recap, an ASP.NET application should have a very limited number of Try
... Catch
blocks. For most cases if an exception occurs it should be allowed to bubble up to the ASP.NET runtime. There are three general cases when Try
... Catch
blocks make sense:
Logging Exceptions and Exception Notification
The most important aspect of exception handling is logging and notification. Whenever an exception happens it needs to be logged to some persistent store (such as a file or database or Windows Event Log) and a developer (or set of developers) should be notified of the exception via e-mail or some other medium. The good news is that implementing such logging and notification is actually quite easy.
Earlier I noted that when an exception percolates to the top of the call stack and reaches the ASP.NET runtime, the configured error page is displayed. In addition to displaying the error page, the ASP.NET runtime also raises its Error
event. It is possible to create an event handler for this event, either through Global.asax
or via an HTTP Module. In the event handler the exception's details can be logged and developers notified.
There's no reason to write your own code to log exceptions as there are existing libraries that handle this for you. My personal favorite error logging and notification library is ELMAH, an open source project created by Atif Aziz. In a nutshell, ELMAH contains an HTTP Module that defines an event handler for the Error
event. When an unhandled exception percolates to the ASP.NET runtime, ELMAH logs its details to a log source provider that you specify in Web.config
. This log source can be a Microsoft SQL Server database, an XML file, a Microsoft Access database, or an Oracle database, among many other options. You can also configure ELMAH to e-mail the error details to one or more recipients. Perhaps ELMAH's most distinctive feature is its built-in, web-based error log viewing page, which enables developers to view a history of errors and error details from a web page on the site. See Simone Busoli's article ELMAH - Error Logging Modules And Handlers for more information on ELMAH.
Another option is Microsoft's own health monitoring, which was added starting with ASP.NET version 2.0. Unlike ELMAH, which focuses on logging errors, ASP.NET's health monitoring can log all sorts of events to any number of log sources. In addition to logging errors, health monitoring can also log application life cycle events (startups, shutdowns, restarts), security-related events, and more. These events can be logged to a Microsoft SQL Server database, to an e-mail message, or to the Windows Event Log. And like with ELMAH, health monitoring can be configured entirely through settings in Web.config
- you don't need to write a line of code. For a good overview of health monitoring check out my article Health Monitoring in ASP.NET, along with Erik Reitan's Health Monitoring FAQ.
How exactly you log errors and notify developers of these errors is not that important. I encourage you to give both ELMAH and the health monitoring system a go. (If you want a high-level overview of the two system, check out the Log and Review Web Application Errors review from my Toolbox column in MSDN Magazine - Toolbox: Logging Web App Errors, Learning LINQ, and More.) What is vitally important, though, is that you have some system in place to log errors and notify developers. Otherwise you'll have no idea if a user encounters an error on the website, let alone how to go about fixing that error should the user take the time to report the error.
Happy Programming!