1.5. Managing Unhandled Exceptions in ASP.NET

How you choose to manage unhandled exceptions in your ASP.NET application makes a big difference to your users, as well as the site's administrators and developers. Presenting the right information, in the right way, to the right people is part of designing a fulfilling overall user experience.

1.5.1. Exception Propagation: Part 2

As you'll recall, earlier I presented a brief discussion about exception propagation. You learned how thrown exceptions are passed up the call stack, and that once an exception reaches the original calling method without being caught, execution stops and the ASP.NET runtime takes over. Let's now take a look at what happens from there.

When the runtime receives the unhandled exception, it raises the Error event for the page that contains the original caller. It searches the page for a Page.Error event handler, and if it finds one, executes it.

If no such handler exists or if the exception is not cleared by the Page.Error event handler, and assuming that the current response has not been re-directed programmatically in Page.Error, the runtime wraps the exception in a new exception of type HttpUnhandledException and raises the HttpApplication.Error event. The HttpApplication.Error event handler is your last opportunity to handle an exception programmatically.

If you haven't defined a handler for HttpApplication.Error or if the exception is not cleared by the HttpApplication.Error event handler, and the current response has not been re-directed in the handler, ASP.NET checks to see if its own built-in exception handler is enabled, and if so, passes control to it. The response is then redirected as specified in the handler's configuration.

If the default ASP.NET exception handler is not enabled, the exception's message, source, and stack trace are dumped to the response stream and the response is terminated. This process is illustrated in Figure 2. (You can think of this figure as a continuation of Figure 1.)

Figure 2. Unhandled exception processing in the ASP.NET runtime.

The very last step in this chain — where details about the exception are dumped to the client — results in the so-called Yellow Screen of Death (or YSOD), as shown in Figure 3.

Figure 3. Yellow Screen of Death.

Examine what the display in Figure 3 reveals. It shows the exception message, the type of exception, the source code file in which the error occurred, a printout of the lines from which the exception was thrown, the exception stack trace, and the version of the .NET Framework you are running on your server. When you first set up a web site — because there are no error event handlers in place and the ASP.NET default exception handler is not enabled — this is how ASP.NET processes (or, more accurately, does not process) an unhandled exception.

This is all very valuable information for a developer trying to debug an error. Unfortunately, it's also very valuable information for a malicious user who might be trying to learn more about your program in order to exploit vulnerabilities in your site! Besides, most innocent users would be pretty confused and intimidated by this display. I'd also point out that the YSOD provides no way for users to interact with it in any meaningful fashion.

Once you agree that the YSOD is not a good way to communicate to your Web audience, it's time to look at what to do about it.

1.5.2. The ASP.NET Default Exception Handler

The good folks at Microsoft also realized the need to prevent spilling the guts of your program to end-users when an unhandled exception occurs. For this reason, ASP.NET comes with a rudimentary built-in handler of its own.

You enable and configure the ASP.NET default exception handler in the customErrors section of system.web in the web.config file, as shown in Listing 17.

Example 17. Enabling the ASP.NET default exception handler
<system.web>
   <customErrors mode="On" />
</system.web>

Listing 17 shows a very simple configuration. There are three allowed values for the mode attribute:

  • Off — This is the same as not having a customErrors section at all. It disables the handler and shows the exception details (Figure 3) to everyone (exactly the situation we are trying to avoid).

  • On — This shows a fixed, built-in error page to everyone, as shown in Figure 4.

  • RemoteOnly — This shows the exception details (Figure 3) for local requests and the built-in page (Figure 4) to remote requests.

Figure 4. ASP.NET default exception handler.

OK, so the built-in page is a little better. At least it doesn't reveal all that exploitable information like before. But it's still confusing, still isn't interactive, still doesn't match the look-and-feel of your site, and is still likely to turn off your users.

Fortunately, there's a way to provide a re-direct to a page of your choosing, as shown in Listing 18.

Example 18. Configuring the ASP.NET default exception handler
<customErrors mode="RemoteOnly" defaultRedirect="Error.aspx">
   <error statusCode="404" redirect="404Error.aspx"/>
   <error statusCode="403" redirect="403Error.aspx"/>
</customErrors>

The defaultRedirect attribute lets you show a custom page for server errors instead of the built-in one, which is another step forward. You can even configure it to show different pages for HTTP errors based on the status code, as shown in Listing 18. And if you set mode="RemoteOnly", you can still allow requests from the local machine to see all the grisly details.

Not bad, but still, the ASP.NET default exception handler has some significant limitations. For one thing, the RemoteOnly option is not of much practical use, since most developers don't debug on the actual production web server. That means that once the site is deployed, you need some other way to indicate which users should see privileged information.

And, of course, even though you can show your own custom error page, you cannot actually access the unhandled exception from that page. Once the redirect to the page takes place, the previous response ends and takes the exception with it. That means that you can't use the ASP.NET handler to log the exception or to send a notification to site administrators.

1.5.3. Page.Error and HttpApplication.Error

Fortunately, from the point where the runtime takes control of an unhandled exception, you have two chances to do some use useful work — the Error events of either the Page object or the HttpApplication object.

The basic process for handling errors in either event is essentially the same:

  1. Call Server.GetLastError to get a reference to the unhandled exception.

  2. Do what you need to do with the exception.

  3. Present some information to the user about what happened.

  4. If necessary, call Server.ClearError to halt further propagation.

To handle the Page.Error event, you wire up an event handler delegate to the Error event right in the code-behind of a page.

In C#, if you have the AutoEventWireup attribute in the @Page directive set to true, you can simply name your handler Page_Error, and the wiring is taken care of for you.

Listing 19 shows how you could handle the exception at the page level, following the basic steps above.

Example 19. Page.Error event handler
protected void Page_Error(object sender, EventArgs e)
{
   // Get a reference to the unhandled exception
   Exception exception = Server.GetLastError();

   // Do some stuff with the exception
   ExceptionUtilities.LogException(exception);
   ExceptionUtilities.NotifyAdmins(exception);

   // Present information to the user
   Response.Write("<h1>Default Page Error</h1>");
   Response.Write("<p>We're sorry, an error occured on this page.</p>");
   Response.Write(
      "<p>You may return to the"+
      "<a href='Default.aspx'>home page</a>"+
      "and try again.</p>");

// Halt propagation
   Server.ClearError();
}

Using a Page.Error event handler is quick and simple, and in some cases, it may be the best option. However, for application-wide exception handling, there are some disadvantages to using this approach. For one, you would have to include this code in every page or place this functionality in a base Page class and derive all your pages from that.

Rather than do that, you can handle the HttpApplication.Error event, which accomplishes the same thing. An HttpApplication.Error handler will allow you to manage any unhandled exception in your application, no matter where it's thrown from. Then, if you need to override the behavior defined there, you can still manage the exception with a page-level handler and clear the error.

The HttpApplication.Error event handler can either go into the global.asax application file, or into an HttpModule. If you use global.asax, you can wire this event to your delegate automatically by naming it Application_Error. Listing 20 shows an example.

Example 20. HttpApplication.Error event handler
private void Application_Error(object sender, EventArgs e)
{
   // Get a reference to the unhandled exception
   Exception exception = Server.GetLastError().InnerException;

   // Do some stuff with the exception
   ExceptionUtilities.LogException(exception);
   ExceptionUtilities.NotifyAdmins(exception);

   // Present information to the user
   Response.Redirect("ErrorPage.aspx");
   // Server.Transfer("ErrorPage.aspx"); // this is an alternative
}

Note the call to Server.GetLastError().InnerException. Remember that the runtime wraps the propagating exception in a new HttpUnhandledException before raising the HttpApplication.Error event. That means that a simple call to Server.GetLastError in this event handler would return an HttpUnhandledException, which is usually not the exception you're specifically interested in. The actual exception you want would be contained in that exception's InnerException property.

Also, instead of writing to the response stream, here you're presenting information by re-directing to a custom error page, just as the ASP.NET exception handler allows you to do. Because the redirect ends the current context, there is no need to explicitly call Server.ClearError.

You've come a long way so far. You've created an unhandled exception mechanism that lets you show a custom error page to your users, allows you to log the exception, and lets you notify system administrators. That's pretty good, but there's still room for improvement.

One problem is that you still cannot work with the exception inside ErrorPage.aspx because the response that carried it is now gone. You could use Server.Transfer instead, which preserves the current context (and saves a round-trip to the client as well), but there are drawbacks there too. Server.Transfer displays the URL of the original requested page instead of the actual URL of the error page you are displaying, which is bound to confuse people. And even then, you haven't yet come up with a good way to distinguish "normal" requests from those of system administrators, so you can't safely display the exception details anyway.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.181.144