Gracefully Responding to Unhandled Exceptions - Processing Unhandled Exceptions
By Scott Mitchell
Introduction
When an unhandled exception occurs in an ASP.NET application, the visitor is, by default, shown
either a "Runtime Error" or
exception details page (which of the two pages is displayed
depends upon the website's configuration and whether the visitor has come through localhost).
Ideally, such esoteric error pages are only shown to developers; regular users should, instead, see a custom, user-friendly
error page. In last week's article, Displaying
User-Friendly Error Pages, we examined the steps to configure an ASP.NET web application to display custom,
user-friendly web pages in response to unhandled exceptions.
The technique discussed in last week's article, however, uses a Response.Redirect() internally to redirect the
user to the custom error page in the event of an unhandled exception. By redirecting the user, the context of the request
is lost (since the redirect causes the browser to send a new request to the error page). Consequently, the custom, user-friendly
error page lacks the ability to garner information about the error that just occurred. Such information might be desired in
order to display a more informative error message.
When an unhandled exception bubbles up to the ASP.NET runtime, the application-level Error event fires. By creating
an event handler for this event, we can access the error details, log the error, notify a developer, or send the user to
the custom error page using Server.Transfer() (which will maintain the context and allow the custom error page
to access the details of the unhandled exception).
In this article we'll examine how to create an event handler for the Error event and examine a free, open-source
component for logging error details. Read on to learn more!
Exploring the Error Event
The HttpApplication class
represents the methods, properties, and events common to all ASP.NET web applications. One event of this class is the
Error event, which is fired
whenever an unhandled exception bubbles up to the ASP.NET runtime. For example, imagine that in an ASP.NET web page you
have code that connects to a database and runs a DELETE statement. If the database is offline when the page is
visited, a SqlException will be thrown. If your code lacks any error handling logic, the exception will bubble up
from your ASP.NET page's code-behind class to the ASP.NET runtime, at which point the Error event will be raised.
When an unhandled exception occurs, we typically want to do two things:
"Process" the error - this may involve logging the error so that it can be inspected later; notifying a developer
of the error by sending her an email; attempting to "fix" the problem
To "process" the error, we first need to create an event handler for the application Error event. The event handler
can be created in Global.asax or handled through an HTTP
Module. In the event handler, details of the unhandled exception can be retrieved using the
Server.GetLastError()
method. Once the error's details have been retrieved, the information can be logged, a developer can be notified, or
control can be passed to a custom, user-friendly error page.
Creating an Error Event Handler in Global.asax Global.asax is an optional file you can
add to your web application's root directory to handle application- and session-level events. To create an event handler
for the application's Error event, start by adding the Global.asax file to your project.
Right-click on the project in Solution Explorer and choose Add New Item and choose the Global Application Class item type
from the dialog box (see the screenshot below).
This will create a dummy Global.asax file with event handlers for the main application-level events - Error,
Start and End - as well as event handlers for the session-level events Start and End.
Since we're only concerned with the Error event, feel free to remove the other event handlers.
In the Error event handler, information about the exception that just occurred can be accessed by calling:
HttpContext.Current.Server.GetLastError()
This returns an Exception object that houses information about the error that bubbled up to the runtime, causing
the Error event to fire in the first place. If the exception was caused from an ASP.NET page, the original exception
is wrapped inside an HttpUnhandledException; the ASP.NET page-level exception can be accessed via the InnerException
property, as we'll see shortly.
Imagine when an unhandled exception occurs we want to email a developer with details of the exception and then redirect the user
to a custom error page, just like we examined in Displaying User-Friendly Error Pages. To accomplish this, we would configure
the <customErrors> settings in Web.config appropriately, and then add the following code to the
Error event handler in Global.asax:
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
'Get exception details Dim ex As Exception = HttpContext.Current.Server.GetLastError()
If TypeOf ex Is HttpUnhandledException AndAlso ex.InnerException IsNot Nothing Then
ex = ex.InnerException
End If
If ex IsNot Nothing Then
Try
'Email the administrator with information that an error has occurred
'!!! UPDATE THIS VALUE TO YOUR EMAIL ADDRESS
Const ToAndFromAddress As String = "email address"
'(1) Create the MailMessage instance
Dim mm As New System.Net.Mail.MailMessage(ToAndFromAddress, ToAndFromAddress)
'(3) Create the SmtpClient object
Dim smtp As New System.Net.Mail.SmtpClient
'(4) Send the MailMessage (will use the Web.config settings)
smtp.Send(mm)
Catch
'Whoops, some problem sending email!
'Just send the user onto CustomErrorPage.aspx...
End Try
End If
End Sub
Pay particular attention to the first few lines of the event handler. Here we access the exception that just occurred
(HttpContext.Current.Server.GetLastError()) and dig down into its InnerException property if needed.
Then, the exception details are emailed to a specified email address using code taken verbatim from a previous article
of mine, Sending Email in ASP.NET 2.0. (The
SMTP relay server details used by this code can be found in Web.config.)
Say that a user visits a data-driven page and there's a problem in connecting to the database. The user will see the configured
error page; behind the scenes, however, a developer will receive an email with the exception details:
Transferring Control - Rather Than Redirecting - to the Custom Error Page
As examined in Displaying User-Friendly Error Pages,
the <customErrors> section of Web.config can be configured to display custom, user-friendly
error pages to visitors when an unhandled exception occurs. Unfortunately, the ASP.NET runtime redirects a user to
the custom error page when an unhandled exception occurs. Specifically, the runtime calls Response.Redirect(customErrorPageUrl),
which sends an HTTP 302 status to the browser, causing it to make a request for the specified URL (customErrorPageUrl).
Since the redirect is a new web request, it has no connection or association with the request that had the unhandled exception.
Consequently, calling Server.GetLastError() from the custom error page will return a null value.
Sometimes, however, we want to have information about the error in the custom error page. Perhaps the message displayed depends
on the error that occurred, or we want to show exception details to authenticated visitors who are in a particular role.
This can be achieved by calling Server.Transfer(customErrorPageUrl) in the Error event handler.
From the docs, Server.Transfer(url)
"terminates execution of the current page and begins execution of a new page using the specified URL path to the page."
In essence, execution of the current request is handed off to another page on the server. There's no expicit redirect message
sent back to the client - it all happens server-side.
Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs)
'... Email details of exception to developer ...
Server.Transfer("~/SmartCustomErrorPage.aspx")
End Sub
With Server.Transfer(), the context is maintained so the custom error page can retrieve information about the
unhandled exception using Server.GetLastError(). From the browser's perspective, there's no change in context either.
That is, the browser's address bar will still show the URL of the web page that caused the unhandled exception, and not
the URL Server.Transfer()ed to.
The download available at the end of this article presents a more intelligent custom error page (SmartCustomErrorPage.aspx).
This error page inspects the exception details and bases its message on the type of exception that occurred.
Handling the Error Event with HTTP Modules
An HTTP Module is a managed component that can respond to events raised by the ASP.NET runtime. The event handler
that responds to the Error event can, alternatively, be located in an HTTP Module. The benefit of using an HTTP Module
is that it provides a "pluggable" component that can be added to an existing ASP.NET application by simply dropping the
HTTP Module assembly in the /bin directory and adding a line or two of configuration to the application's
Web.config.
Creating and configuring an HTTP Module for handling errors is beyond the scope of this article. Instead, let me heartily recommend
a free, open-source library provided by Atif Aziz, called Error Logging
Modules And Handlers, or ELMAH. ELMAH is composed of an HTTP Module for logging unhandled exceptions to
a data store and an HTTP Handler for displaying exception details in a web page or RSS
feed.
ELMAH can be downloaded from its GotDotNet Workspace, http://workspaces.gotdotnet.com/elmah.
For a thorough discussion on setting up and installing ELMAH, check out Using
HTTP Modules and Handlers to Create Pluggable ASP.NET Components. Setting up ELMAH takes 60 seconds or so, and provides
a detailed log of exceptions, rich notification to developers, and user-friendly web pages for viewing exception details online.
Highly recommended!
The download available at the end of this article uses the ELMAH library to log errors to a SQL Server 2005 Express Edition
database...
Conclusion
When an unhandled exception occurs in a web application, we typically want to ensure that the end user sees a custom, user-friendly
error page, and that the details of the exception are "processed." Processing the exception can differ for each application, but
typically involves logging the exception and notifying a developer. A previous article, Displaying
User-Friendly Error Pages, examined how to display a custom error page. In this article we saw how to create an event
handler for the application Error event.
Rather than create your own exception logging and notification system, consider trying ELMAH, a free, open-source library
that includes an HTTP Module for logging unhandled exceptions and another optional one for sending an email with the exception's
details.