Gracefully Responding to Unhandled Exceptions - Displaying User-Friendly Error Pages
By Scott Mitchell
Introduction
In .NET applications, an illegal operation - an invalid cast, attempting to reference a null value, trying to connect to a database
that's been taken offline, and so on - raises an exception. Exceptions can be caught and handled directly in code
through the use of Try / Catch blocks. For ASP.NET applications, if the exception is
not handled in code, it bubbles up to the ASP.NET runtime, which raises an HttpUnhandledException.
By default, unhandled exceptions result in a page that displays the text, "Runtime Error" with instructions for developers on how to display exception details
(see the screen shot to the right). This "Runtime Error" error page is what is seen by external visitors; if you visit your site through
localhost and an unhandled exception occurs, the default error page includes the type and details of the exception thrown.
[View a screenshot]
End users will no doubt find the "Runtime Error" page to be intimidating and confusing - do you think the average computer
user knows what "Runtime" means? All the user knows is that something went horribly wrong. They might fear
that their data or progress has been lost and that they are responsible for the error. Ironically, the person who does care
that an unhandled exception has occurred - the developer - is left out of the loop unless the end user takes the time to
email the developer the details of the error (what page it happened on, the steps the user had performed that caused the
error, and so on).
Fortunately, ASP.NET provides solutions to these two problems. An ASP.NET application
can be configured to automatically redirect the user to a less-intimidating page that explains that there has been a problem.
This custom, user-friendly error page can omit such lingo like "Runtime" and have its look and feel match the website's.
Additionally, there are techniques available to log and alert the developer of the unhandled exception.
In this article we'll look at how to display user-friendly error pages in the event of an unhandled exception.
A future article (Processing Unhandled Exceptions) will examine how to log and alert the site administrator when such exceptions occur. Read on to learn more!
Displaying a User-Friendly Error Page in the Face of an Unhandled Exception
When an unhandled exception bubbles up to the ASP.NET runtime, the application's
<customErrors> settings are
consulted. These settings - specified in the application's Web.config
file - determine whether a visitor sees the "Runtime Error" page, a page with details about the exception, or some custom,
user-friendly error page. What action is taken depends upon the <customErrors> setting's mode
property, which can have one of the following three values:
On - the "Runtime Error" page or custom, user-friendly error page is shown to all visitors in the face of an unhandled exception
Off - the exception details page is shown to all visitors in the face of an unhandled exception
remoteOnly - remote users - those not coming through localhost - see the "Runtime Error" page or custom, user-friendly
error page; local visitors - the developers, typically - see the exception details page
To display a user-friendly error page instead of the default "Runtime Error" page, set the <customErrors>
setting's defaultRedirect property to the URL of the user-friendly error page. The user-friendly error page can
be a static HTML page or an ASP.NET page; it can be an absolute URL (like http://www.someserver.com/SomePage.htm) or relative
to your website. Relative URLs can use ~ to base the file path at the root of the web application (such as
~/ErrorPage.aspx).
Let's look at an example. The following <customErrors> settings displays the user-friendly error page GeneralServerError.aspx
to all visitors in the face of an unhandled exception:
By changing mode to remoteOnly, we could configure it so that only remove visitors saw GeneralServerError.aspx in
the face of an exception. Those coming through localhost would see details of the exception.
When an unhandled exception bubbles up to the ASP.NET runtime, the runtime notes the <customErrors> settings
and internally issues a Response.Redirect() to the defaultRedirect. This sends a 302 HTTP status code down to
the browser, which instructs it to request the specified URL (GeneralServerError.aspx, in this example). The runtime
also appends to the specified URL a querystring parameter named aspxerrorpath, which refers to the URL the user
was visiting when the unhandled exception was raised.
The user-friendly error page - GeneralServerError.aspx - can display some message to the user explaining that
there's a problem.
The download available at the end of this article includes a sample ASP.NET 2.0 web application that illustrates using the
<customErrors> settings...
Redirecting Users to Different Pages Depending on the Error
If you specify a user-friendly error page via the <customErrors> settings defaultRedirect property,
a user will be directed to that web page when any unhandled exception bubbles up to the runtime. This includes both
internal server exceptions - which have a status code of 500 - and HTTP-related exceptions, such as 404 page not found exceptions. The <customErrors> settings
can be customized to direct the user to a different web page depending on the HTTP status code of the exception. That is,
you can have one page displayed in the face of a 404 error, and another in the face of an internal server exception.
To customize the <customErrors> settings, add an <error>
element detailing the page to send users to when an exception from a specific HTTP status code is raised. You may include
zero to many <error> elements. If the status code of the raised exception is not found in the list of
<error> elements, the user will be sent to the page specified by the defaultRedirect property.
For example, the following <customErrors> settings will send the user to FourOhFour.aspx if
they request a page that is not present, and to GeneralServerError.aspx for any other type of unhandled exception:
Such a 404 page can determine the non-existent page that was requested through the aspxerrorpath querystring field.
In other words, if a user attempts to visit a non-existent page - www.server.com/NoSuchPage.aspx - the ASP.NET
runtime will redirect the user to www.server.com/FourOhFour.aspx?aspxerrorpath=/NoSuchPage.aspx. Additionally,
we can determine whether the user requested the non-existent web page by clicking on a link from some other page. In such a
case, the initial page has a broken link that needs to be fixed! You can determine if the user reached the non-existent
page from another page by consulting the Request.UrlReferrer property in ASP.NET 2.0 or the Request.ServerVariables("HTTP_REFERER")
variable in ASP.NET 1.x.
Keep in mind that the <customErrors> settings can only be applied by the ASP.NET runtime when an unhandled
exception occurs when requesting an ASP.NET resource. For example, when using IIS as your web server, requests to static resources,
such as HTML pages, are handled by IIS and are not handed off to the ASP.NET engine. Therefore, if the user attempts to visit a
non-existent HTML page, IIS will respond with a 404 status code instead of redirecting the request to the page dictated by
the <customErrors> settings. To display the same error page as specified in the <customErrors> settings,
you can create a custom 404 page in IIS or
map HTML pages to the ASP.NET engine.
Note that the ASP.NET Development Web Server that ships with Visual Studio 2005 and is used for file system-based websites
maps all resources to the ASP.NET engine. Therefore, the <customErrors> settings will be used when
requesting an HTML page...
Improving the 404 Error Page
The FourOhFour.aspx error page succeeds in displaying a user-friendly message to the end user informing them
that they've attempted to visit a non-existent page. With a bit of code we can greatly extend to utility of the 404 error page.
For example, if a user reaches a non-existent web page through a broken link on some other page, it would be nice to shoot an email
to the developers so that they can fix the broken link. For information on how to send an email from an ASP.NET web page,
see Sending Email in ASP.NET 2.0 or
Sending Email from an ASP.NET 1.x Web Page.
Another possible enhancement would be to build a database table that includes mappings from invalid URLs to valid ones. In
the 404 error page, a database lookup would be performed to determine if the non-existent page requested has a
"valid" URL in the table. If so, the user would be automatically redirected to the valid URL. Such functionality would be
a handy way to deal with the following situation: imagine another website has a link to a particular
page on your website, but the link has a typo making it a broken link. Ideally, the link on the other website would be fixed,
but rather than wait for someone else to fix the problem, you could proactively map the mistyped URL to the intended page.
Then, when a user came to your site through the other website's broken link, they'd automatically be redirected to the
correct page!
Conclusion
In the face of an unhandled exception, the ASP.NET runtime can take one of three actions: show the generic "Runtime Error"
page; display the exception's details; or display a custom, user-friendly error page. By default, remote visitors are shown the
"Runtime Error" page and those visiting through localhost - developers, typically - see the exception details page. However, the
default "Runtime Error" page can easily be replaced with a custom error page. These settings can be customized through the
<customErrors> element in Web.config.
One issue with custom, user-friendly error pages specified through the <customErrors> settings is that they cannot
obtain details about the exception that just occurred other than the page on which the exception occurred (through the
aspxerrorpath querystring field). Ideally, the custom error page would be able to log the exception's details
or perhaps customize the message displayed to the end user based on the exception.
Workarounds to these shortcomings are discussed in Processing Unhandled Exceptions,
along with a discussion of a free, open-source library that can automatically log and notify developers about unhandled
exceptions!