Monday, February 7, 2011

How to Fix Error Handling in ASP.NET

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)
{
// Get the last exception thrown
Exception ex = Server.GetLastError();

// Do something with the exception like logging etc.

// Set the error page
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.
Collapse
private void BasePage_Error(object sender, EventArgs e) { // Get the last exception thrown Exception ex = Server.GetLastError(); // Do something with the exception like logging etc. // The statement below has no significance - it's as good as commented this.ErrorPage = "/ErrorHandling/ErrorPages/BaseError.html"; // Throw the error to prevent wrapping 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.