When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, August 12, 2009

Exception Handling Advice for ASP.NET Web Applications

By Scott Mitchell


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!

- continued -

The Crib Notes
My advice for handling exceptions in an ASP.NET application can be boiled down to the following guidelines:
  • Create and use a meaningful custom error page.
  • In general, do not catch exceptions. Let them bubble up to the ASP.NET runtime. Some cases where catching an exception makes sense include:
    • When there is a plausible way to recover from the exception by performing some alternative logic,
    • When a peripheral part of the application's workflow throws and exception and that exception should not derail the entire application, and
    • When you need to include additional information with the exception by throwing a new exception that has the original exception as its inner exception.
  • Log all exceptions to some persistent store and use email (or some other medium) to notify developers when an exception occurs in production. Consider using ELMAH or ASP.NET's built-in Health Monitoring system to facilitate this process.
Read on for a more in-depth look at these suggestions.

The Yellow Screen of Death!!

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:

  • Whether the website is being visited locally or remotely, and
  • The <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:
  • what happened
  • what will be affected
  • what the user can do from there
  • and any valuable support information
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.

A detailed custom error page.
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"
              defaultRedirect="~/YourCustomErrorPage.aspx" />

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:
  • Re-throw the exception, as in:

    Try
       ...
    Catch
       Throw

    End Try

    Or:

    Try
       ...
    Catch ex As Exception
       Throw ex

    End Try

  • Swallow the exception, as in:

    Try
       ...
    Catch
       ' Do nothing, swallowing the exception
    End Try

  • Use the Catch block to return a status code, as in:

    Try
       ...
    Catch
       Return -1
    End 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:

  1. Try to send the email message via the primary SMTP server...
  2. If an exception is thrown from Step 1 then first log the exception and try to resend the email, but this time use the secondary SMTP server...
  3. If an exception is thrown from Step 2 then let this exception percolate up the call stack
This workflow could be implemented with the following pseudocode:

Try

   ... Try Sending Email Using Primary Email Server ...

Catch smtpEx As SmtpException

   ... Log Failure Sending to Primary Email Server ...

   ... Try Sending Email Using Secondary Email Server ...

End 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:

  1. Try to send the email message via the primary SMTP server...
  2. If an exception is thrown from Step 1 then first log the exception and try to resend the email, but this time use the secondary SMTP server...
  3. If an exception is thrown from Step 2 then log it and complete the order processing
This updated workflow could be implemented with the following pseudocode:

Try

   ... Try Sending Email Using Primary Email Server ...

Catch smtpEx As SmtpException

   Try

      ... Log Failure Sending to Primary Email Server ...

      ... Try Sending Email Using Secondary Email Server ...

   Catch smtpEx2 As SmtpException
      ... Log the exception and continue on ...
   End Catch
End 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:

  • When there is an opportunity to recover from the application by taking some alternative course of action.
  • When the exception indicates a periphery problem that should not impede the current workflow. Keep in mind that all exceptions should be logged; do not swallow an exception, but rather log it before continuing on. (We'll talk about exception logging and notification momentarily.)
  • When additional information needs to be included with the exception. In this case it is useful to catch the exception and then throw a new exception with the original exception as an inner exception.

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!

  • By Scott Mitchell


    Further Reading


  • Gracefully Responding to Unhandled Exceptions - Displaying User-Friendly Error Pages
  • Displaying a Custom Error Page
  • Gracefully Responding to Unhandled Exceptions - Processing Unhandled Exceptions
  • Design Guidelines for Exceptions
  • ELMAH Download / Project Page
  • ELMAH - Error Logging Modules And Handlers
  • Logging Error Details with ELMAH
  • Health Monitoring in ASP.NET
  • Logging Error Details with ASP.NET Health Monitoring
  • Exception Handling Best Practices in ASP.NET Web Applications
  • Euphemized Exceptions for .NET 4.0 (HUMOR)


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article