Introduction
When errors occur in an ASP.NET application, they either get handled or propagates unhandled to higher scopes. When an unhandled exception propagates, the user may be redirected to an error page using different ASP.NET configuration settings. However, such a redirection may be prevented in the first place by handling the exceptions that get thrown. Error handling in ASP.NET therefore, may be divided into two separate logics:
- Redirecting the user to an error page when errors go unhandled.
- Handling exceptions when they get thrown.
Redirecting the user to an error page
There are two different scopes where we could specify which page the user should be redirected to, when errors go unhandled:
- Page level (applies to errors that happen within a single page).
- Application level (applies to errors that happen anywhere in the application).
Page Level
Use the errorPage
attribute in the webform.
This attribute defines the page the user should be redirected to when an unhandled exception occurs in that specific page. For example,
<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="WebTest.WebForm1"
errorPage="/WebTest/ErrorPages/PageError.html"%>
The errorPage
attribute maps to the Page.ErrorPage
property, and hence may be set programmatically. The value may optionally include query string parameters. If no parameters are added, ASP.NET would automatically add one with the name aspxerrorpath
. This parameter would hold the value of the relative URL to this page, so that the error page would be able to determine which page caused the error.
If a value is specified in this attribute (or property) and an unhandled exception occurs in the page, the Page
class would automatically perform a redirect to the specified page. If a value is not specified, the exception is assumed to be unhandled, wrapped in a new HttpUnhandledException
and then thrown, propagating it to the next higher level.
Application Level
Use the customErrors
section in web.config.
This section lets you specify the error page to which the user should be redirected to when an unhandled exception propagates in the application level. This section specifies error pages for both default errors as well as the HTTP status code errors.
<customErrors mode="On" defaultRedirect="/WebTest/ErrorPages/AppError.html">
<error statusCode="404" redirect="/WebTest/ErrorPages/404.html" />
</customErrors>
The mode
attribute specifies whether to show user-defined custom error pages or ASP.NET error pages. Three values are supported for this attribute:
RemoteOnly
- Custom error pages are shown for all remote users. ASP.NET error pages with rich error information are displayed only for local users.
On
- Custom error pages are always shown, unless one is not specified. When a custom error page is not defined, an ASP.NET error page will be displayed which describes how to enable remote viewing of errors.
Off
- Custom error pages are not shown. Instead, ASP.NET error pages will be displayed always, which will have rich error information.
It's a bad idea to give users more information than what is required. ASP.NET error pages describe technical details that shouldn't be exposed. Ideally, the mode
attribute thus should not be set to Off
.
The defaultRedirect
attribute specifies the path to a generic error page. This page would typically have a link to let the user go back to the home page or perform the request once again.
Each error
element defines a redirect specific to a particular HTTP status code. For example, if the error is a 404 (File Not Found), then you could set the error page as FileNotFound.htm. You could add as many error elements in the customErrors
section as required, each of which specifies a status code and the corresponding error page path. If ASP.NET can�t find any specific error element corresponding to a status code, it would use the value specified in the defaultRedirect
attribute.
Notes
- The settings specified in the page level (
errorPage
attribute) would override those specified in the customErrors
section. The reason is because errors in the page would be handled by the Page
class first, which might thus prevent the exception from being propagated to the application level. It�s only when the Page
class fails to handle the exception that the values set in customErrors
come into scope.
- All these settings mentioned above apply only for requests that are made for ASP.NET files. More specifically, these settings would work only for requests for files with extensions that are mapped to the aspnet_isapi. For example, if you request for an ASP or JPG file (extensions that are not mapped to aspnet_isapi) which does not exist, then these settings won�t work, and the standard error page specified in IIS would be displayed. To modify this behavior, either map the required extensions to aspnet_isapi or modify the custom error pages specified in IIS.
Handling exceptions
There are different levels where you could handle exceptions.
- Locally (method level), where exceptions could be thrown.
- Page level by handling the
Page.Error
event.
- Application level by handling the
HttpApplication.Error
event.
- HTTP Module level by handling the
HttpApplication.Error
event.
Local error handling
Wrap code that might throw exceptions in a try
-catch
-finally
block.
If you can recover from the exception, then handle it in the catch
block. If the exception cannot be recovered from locally, let the exception propagate to higher levels by throwing it. If the exception cannot be recovered from locally, but additional information can be provided, then wrap the exception with the new information and throw the new exception. This method is used when you use custom exceptions. Place the clean up code in the finally
block.
Find more information on exception handling best practices available in MSDN.
Note: The more exceptions you catch and throw, the slower your application would run. This is more significant in web applications.
Page Level
Attach a handler to the Page.Error
event. In C#, you will have to write the event wire up code yourself in the Page_Load
method.
When an exception goes unhandled in a page, the Error
event of the Page
class gets triggered.
Typically, the first action you would perform in this handler would be to obtain the exception thrown, by using the Server.GetLastError
method. This method would return a reference to the last Exception
object that was thrown.
After you get the Exception
object, you will want to redirect the user to an error page. We could make ASP.NET do the redirection by using the errorPage
attribute of the Page
(design time) or by using the Page.ErrorPage
property (runtime). Obviously, the choice here would be to programmatically set the value using the Page.ErrorPage
property in the event handler.
private void WebForm1_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
this.ErrorPage = "/ErrorHandling/ErrorPages/BaseError.html";
}
If you do not specify an error page, the exception gets wrapped inside an HttpUnhandledException
object and propagates. If you don�t want the exception to be wrapped, then simply throw the last exception, which would force immediate propagation escaping any intervention. However, this would prevent ASP.NET from redirecting the user to a page specific page either. In other words, if you are going to throw the last error (or any exception for that matter), setting the error page will have no effect.
private void BasePage_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
this.ErrorPage = "/ErrorHandling/ErrorPages/BaseError.html";
throw ex;
}
To reduce redundant code, you could define a base web form page which defines the Page.Error
event handler and then wire up code in the constructor, and then make all your Web Form pages derive from this base page. This would save you the effort of writing the error handler in each web form.
Application Level
Attach an event handler to the Application.Error
event.
When an unhandled exception leaves a page, it gets propagated to the application level, which would trigger this event.
There are two things you would want to do in an application error handler.
- Get the last exception thrown using
Server.GetLastError
.
- Clear the error using
Server.ClearError
, to inform ASP.NET that you have handled the error.
If you don�t clear the error, the exception would propagate. However, since there isn't any higher scope where the exception could be caught, ASP.NET is forced to handle it. The way ASP.NET handles the exception depends upon the settings specified in the customErrors
section we saw before. If no settings are defined, ASP.NET would use the defaults and display the infamous 'yellow' error page.
HTTP Module Level
Instead of handling application errors in global.asax, exceptions may also be handled by attaching an HTTP Module which would have a handler attached to the Application.Error
event. This method would be triggered before the corresponding application handler would be invoked. Such an implementation would be beneficial if you have multiple projects with the same global error handling implementation. In such a scenario, you could create a module and attach it to each web application you have.
All the points we saw in the Page and Application handlers apply to the Module handler as well.
Important Notes
Prevent infinite recursion
If an error occurs in the error handling code, an infinite recursive loop would result, which would soon drag your server down. The reason why this happens is because the new exception would trigger the error event once again which would in turn redirect control to the handler, which would cause yet another exception to be thrown, making an infinite loop.
This might also happen if the error page itself throws an exception. To counter this possibility, making error pages static is a good idea.
Errors may also happen while attempting to redirect to an error page using Server.Transfer
or Response.Redirect
maybe due to an invalid path. To tackle this scenario, we could wrap the redirection code in a try
-catch
block. If the redirection fails, then we have nothing more to do other than setting the response code and completing the response, using the Response.StatusCode
property and the HttpApplication.CompleteResponse
method. This would then be handled by the settings specified in the customErrors
section.
Parser Errors
Parser errors are caused due to invalid tags (or similar reasons) in an aspx page. These errors are usually of type HttpParseException
. Such errors will not be caught by the Page level handler as page parsing happens before ASP.NET creates the assembly for the aspx page. In other words, parser errors are thrown while ASP.NET reads the aspx file and tries to create its assembly, and hence is way before the corresponding type is created. Thus, such errors will have to be handled in the application scope.
Exception logging and response time
Users need to get responses as quick as possible. Implementation wise, this means that when errors happen, error recovery processes should be quick and users should be redirected or informed of the error as soon as possible. If exceptions are going to be logged to a file or other mediums, then it could take time which would lead to a slow response. Making exception logging an asynchronous process would be a good idea in this respect.
Source Code
The source code is in VS.NET 2003 and the virtual directory is named ErrorHandling. The code demonstrates most of the implementations this article talked about. A few of the items would require you to uncomment and build again, as mentioned in the corresponding sections.