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).
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"
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.
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" />
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
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.
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
- 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.
There are different levels where you could handle exceptions.
- Locally (method level), where exceptions could be thrown.
- Page level by handling the
- Application level by handling the
- HTTP Module level by handling the
Local error handling
Wrap code that might throw exceptions in a
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
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.
Attach a handler to the
Page.Error event. In C#, you will have to write the event wire up code yourself in the
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";
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.
Attach an event handler to the
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
- 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.
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
Response.Redirect maybe due to an invalid path. To tackle this scenario, we could wrap the redirection code in a
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
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.
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.