Skip navigation

Build Generic Error Handlers

Learn how to create centralized error handling in your applications to foster a “gentler” user experience.

asp:feature

LANGUAGES: VB .NET | C#

TECHNOLOGIES: Error Handling | CLR | Exceptions

 

Build Generic Error Handlers

Learn how to create centralized error handling in your applications to foster a "gentler" user experience.

 

By Don Kiely

 

As a longtime Visual Basic user, I've always been a bit appalled that the unstructured On Error handler was my only way to write robust, bulletproof code. VBScript took error handling to new lows, limiting my ASP pages only to inline error handling. As a long reformed C++ programmer, I knew there were better ways.

 

With .NET and its Common Language Runtime (CLR), all the .NET languages sport structured error handling. Microsoft also endowed ASP.NET with some great features for recovering gracefully when things in your Web application go awry. Combining these ASP.NET features with those in the .NET Framework can yield amazingly robust applications.

 

Here's the best part: You finally have all the tools you need to create centralized error handling in Web applications. Even better, you can do it without writing and rewriting a ton of scripting code like you had to back in the old days of ASP. In this article, I'll explain how I built just such a generic error handler for unhandled errors that, despite your best efforts, slip through the cracks in an application. My solution isn't the prettiest - no one ever accused me of being a graphics designer - but I hope the code and ASP.NET techniques it employs provide a foundation that works for the kind of apps you write. Hopefully you'll hatch ideas of your own about other ways to make your app's user experience more pleasant - even when things go wrong. Along the way, I'll touch on and use some advanced techniques from throughout the .NET Framework. I'll talk about the ways that I put a technology to use, although most of them deserve an article of their own if not an entire book.

 

ASP.NET and the CLR: Working Together

In my article Kill Bugs Dead!, I covered the basic error-handling techniques you should use throughout a Web application. These build on the principle that you generally should handle most exceptions in software as close to the source of the problem as possible. So, in a Web page, you can handle exceptions at the procedure level, with the .NET Try-Catch-Finally blocks; at the Page level, with the Error event or a Page directive; at the Application level, with the Error event in global.asax; or you simply can punt and let ASP.NET fall back on its default exception handling. (You can customize default handling via settings in web.config, including redirecting users to a Web page of your own design if an unhandled exception slips through.)

 

That article covered the basics, so now I'm going to put them to use. I had several design requirements for the centralized error handler, including the information available to the user and to the developer. For the user:

  • A user-friendly explanation in English of what the problem is, whether it is solvable, and what can be done
  • Date and time of the exception
  • Ways the user can get immediate help if needed
  • A field where the user can enter pertinent information about what they were doing when the problem arose
  • A field where the user can enter contact information in case the developer needs to reach them

 

Although that kind of information is useful, it doesn't give the developer much to go on to solve the problem. But giving the user more can be a serious breech of security. Displaying stack traces and other information can impress a user with a developer's incredible intelligence, though it virtually guarantees someone will hack the application. So the developer needs a lot more information to solve the problem without exposing it to the user:

  • Stack trace
  • Information about the environment and application state
  • Whether it is a database exception
  • Connection and command information
  • Custom information specific to an application and the current procedure
  • Everything else that will help the developer solve the problem hours after the user has logged off the system
  • Options configurable through web.config
  • The ability to intrude on the ASP.NET application with as light a touch as possible

 

Figure 1 shows the solution I devised. When an unhandled exception rears its ugly head, the application presents the user with a warm and fuzzy Web page explaining that something went horribly wrong, using English or the local vernacular of choice and minimal technobabble.

 


Figure 1. This is the Web page the user gets when the application experiences an unhandled exception. Notice that, in the interest of security and protection from the bad guys, it doesn't display much technical information.

 

The user can opt to hit the browser's back button to try the operation again - optimist! - or send an error report to the development team. In these days of heightened awareness of security and privacy issues, I didn't want simply to include a Submit button that sent, from the user's perspective, all kinds of personal information. So the user can click on the View Report Before Submitting button to view the information at the bottom of the page (see Figure 2).

 


Figure 2. Few users will understand the gobbledygook the application will send to the development team, but at least they can review it and make sure it isn't transmitting their Charles Schwab online account password.

 

The exception page intrudes on the ASP.NET application with as light a touch as possible. I'm sure there are probably a few better ways of doing things, but this version of the page minimizes customizations in the application. It does so by including customization settings in the AppSettings section of web.config, one line of code added to global.asax, and one module added to the project (well, really two with the ASPX page's code-behind file). If you were really determined to minimize the touch, you probably could work this down a bit.

 

Create Custom Exceptions

The System.Exception class is the base class for all exceptions built into the .NET Framework. Whenever something goes wrong in a .NET application, the CLR raises the most appropriate exception for the problem. All these exceptions derive from either System.SystemException or System.ApplicationException, which are, in turn, derived from System.Exception. Figure 3 displays a small portion of the dozens of exceptions defined in the .NET Framework.

 


Figure 3. System.Exception is the mother of all .NET exceptions. Most of the built-in exceptions derive from System.SystemException, but you should derive your own custom exceptions from System.ApplicationException. There's no functional difference between the two; it's simply a way to keep exceptions organized.

 

Normally, you should do your best to handle exceptions raised in and by your code. But there are three exceptions that cause special concerns: StackOverflowException, OutOfMemoryException, and ExecutionEngineException. As their names suggest, these are catastrophic exceptions from which you can't recover. You can catch them in a Try-Catch-Finally block, but you always should re-throw them so ASP.NET terminates the application. Even if you catch these exceptions and do something you think will help, your application - and perhaps ASP.NET itself - will be in an unstable state. Don't tempt fate on these.

 

Even if you are throwing your own error in response to a problem at run time, try to use one of the built-in .NET exceptions if you can. Microsoft has done all the work for you, and you can add your own customized error message to the exception. For example, say you have a procedure with a division calculation. If the divisor is zero and you don't catch the exception, the CLR raises DivideByZeroException, which derives from ArithmeticException. You can wrap a custom exception around the .NET exception and customize it to your heart's content. An exception's InnerException property still gives you access to the next exception up in a hierarchy of nested exceptions, and GetBaseException accesses the raw exception that caused the problem in the first place.

 

Deriving a new exception is what I did in the generic error handler. Start by creating a new class in your ASP.NET project (all the code in this article is written in VB .NET, but you can download a C# version as well):

 

_

Public Class aspnetPROException

   Inherits ApplicationException

 

All .NET exceptions are serializable, so you can remote them across process boundaries, and the attribute on the class keeps that feature for the custom exception. The class inherits from ApplicationException, one of the two exception types shown in Figure 3.

 

Inheriting from ApplicationException, and thereby from System.Exception, gets you many nice features for your custom exception. In particular, you inherit the InnerException property for nesting exceptions; the Message property that provides a description of the exception; and support for serialization.

 

I wanted to carry along several other pieces of information along with the custom exception, so I defined private class variables for things such as the application name, date and time of the occurrence, and contact information for the responsible developer so the user could reach someone for more information. I also added the appropriate Property procedures to expose these pieces of information to the rest of the application.

 

When building custom exceptions, you should include at least three constructors that mirror the underlying Exception objects, as well as a fourth if you need to support serialization. First is the default constructor; nothing terribly interesting here:

 

Public Sub New()

   MyBase.New()

End Sub

 

Second, it is common to instantiate an exception and pass in a string for the value of the Message property. This is one way to use built-in exceptions while customizing the description the user sees:

 

Public Sub New(ByVal sMessage As String)

   MyBase.New(sMessage)

End Sub

 

The third constructor lets you pass in a custom message as well as a reference to another exception. This is useful when the application has raised an exception, you've caught it, and you want to raise a custom exception of your own. You can grant the code that handles the error farther up the call stack access to the original error by providing a value for the eInner parameter:

 

Public Sub New(ByVal sMessage As String, _

   ByVal eInner As Exception)

   MyBase.New(sMessage, eInner)

End Sub

 

The fourth constructor is optional, but it is required if you plan to support serialization, which is useful for passing objects within and between applications. The underlying exception objects are serializable already, but you must provide support for any custom properties you implement as part of the custom exception. I won't cover this any further here, but the serialization constructor is included with this article's code download.

 

The rest of the code in the aspnetPROException class exposes the Message and StackTrace properties of the base class, as well as Property procedures to expose the custom properties.

 

Many of the original values used to populate the custom properties of the class come from web.config in the AppSettings section. (This application would be a good use of custom application sections, but I'll save that topic for another article.) Here are the initial settings:

 

   

      value="asp.netPRO Exception Page" />

   

      value="[email protected]" />

   

   

   

   

   

 

You'll need to customize these for use with your environment and application. I'll mention each of these settings as we come to them in the code.

 

At this point, you have a custom exception called aspnetPROException. The namespace for this project is aspnetPROExceptions, so the full object reference is aspnetPROExceptions.aspnetPROException. You easily can encapsulate the custom exception in its own namespace, then use a standard name in your code when referencing the exception.

 

So now you're writing the application and come to a situation where you need a Try-Catch-Finally block and a custom exception. The sample project does this in the Click event procedure of a button on a Web form. It's highly contrived, but it gets the idea across. In this case, the variable y in the Try block is zero, which raises a DivideByZeroException and a jump to the appropriate Catch block:

 

Try

   z = x / y

   zi = x \ y

Catch exc As DivideByZeroException

   Dim aspExc As New _

      aspnetPROException( _

         "y is zero in the Big Calculation", exc)

   aspExc.PlainDesc = "The value you entered ..."

   Throw aspExc

End Try

 

The Catch block that catches the DivideByZeroException exception first instantiates a new instance of the aspnetPROException. Notice that this uses the third form of the constructor, passing in a custom message and the original exception the code raised. Then the code assigns a technobabble-free description of the problem to one of the custom properties, PlainDesc. Finally, the code throws the custom exception up to whatever code will handle it.

 

But there is no custom code to handle it because this exception is thrown from an event procedure. Events are not called by other code, strictly speaking, so ASP.NET must handle the exception as best it can.

 

Deal With Unhandled ASP.NET Exceptions

So far, you've created a custom class that inherits from ApplicationException in the .NET Framework. You can use it anywhere you need it, either in response to an exception built into the Framework (as the previous code demonstrates) or anytime a procedure needs to raise a custom exception. All this applies to any kind of .NET application, not only ASP.NET.

 

In an ASP.NET application, when an unhandled exception occurs you generally want to display some kind of page that indicates a problem as gently and helpfully as possible. In the previous click event procedure code, had I simply displayed a message on the current page about something the user could fix rather than raising the aspExc exception, it would have been a handled exception. You must be very clear on the distinction.

 

At this point, you can use any of ASP.NET's many mechanisms to respond to the unhandled exception, most of which I discussed in my previous article. For this generic error-handling page, I saved the exception to a global variable, gEXC, in the Application_Error event procedure in global.asax. I also set the default redirect setting in web.config to ErrorPage.aspx, which is the generic error page. I just as easily could have done a Response.Redirect in the Application_Error procedure or used another technique for ensuring the exception is available in the ErrorPage module.

 

I declared gEXC as type Exception so the page could be used with any kind of unhandled exception. The first thing ErrorPage.aspx does when it loads is assign gEXC to a module variable, casting it to the aspnetPROException type, so the code is able to access the custom properties of the class:

 

Dim mEXC As aspnetPROException = _

   CType(gEXC, aspnetPROException)

 

The Page_Load event procedure in ErrorPage.aspx basically does user-interface stuff, presenting the information shown in Figure 1. It reads the custom properties of the mEXC object variable and puts them on the page. It also checks to see whether the AllowUserPreview setting allows users to preview the error information that will be sent if they submit it. If the setting is "Yes," the code makes the View Report Before Submitting button visible.

 

The rest of the code in ErrorPage.aspx reads information from web.config so it knows where to send an e-mail, whether it should record something in the event log, and so forth. It also saves the exception information to a .dat file in a Reports subdirectory of the application.

 

This code uses a BuildErrorReport procedure to build a text version of the exception with all the technobabble a developer needs to diagnose and solve the problem. The code in that procedure reads properties of the mEXC object and uses a StringBuilder object to concatenate it all together into a single string. It adds the description, name, phone number, and e-mail address the user entered on the form (if any). It also digs into the exception, recording the message, stack trace, and other information provided by the base Exception object - not stuff you want the user to see, unless they really want to!

 

Most importantly, BuildErrorReport is a recursive procedure. Any exception can have an InnerException property, and the aspnetPROException contains the original exception raised when the code attempted to divide by zero. So, if InnerException is not Null (C#) or Nothing (VB .NET), the procedure calls itself, passing in the exception reference by the InnerException property along with an incremented iLevel value to keep track of how deeply the call is nested:

 

If Not exc.InnerException Is Nothing Then

   sb.Append(BuildErrorReport(exc.InnerException, _

      iLevel + 1))

End If

 

The ViewReport procedure also calls a BuildReportStream procedure, which serializes mEXC to both a memory stream and a file stream. The memory stream is required for attaching the exception to an EventLog entry, and the file stream is needed to attach it to an e-mail message. The latter file is named using the current SessionID and a .dat extension. This is all pretty simple with .NET's Stream objects.

 

Once the report streams are available, ViewReport either sends them on their way if the user clicked on the Submit button, or it simply blasts them to a previously invisible textbox for the user to review (but not change). If the user chooses to submit the report, the code either writes an Event Log entry or sends an e-mail - or both, depending on the settings in web.config. That code is pretty standard, so I won't cover it here.

 

One end result is the event log entry shown in Figure 4. Notice that because I attached the memory stream, the event log entry contains the byte data you can review. The e-mail sent contains the error report as the text of the e-mail message and the full serialized exception object as a .dat file attachment. With either technique, you easily could write an application to re-create the exception object and examine it more closely for clues about what went wrong.

 


Figure 4. Using the Windows event log is a useful technique for gathering exception information from an ASP.NET application. You can view the results in the Computer Management applet, or you can access them programmatically and rehydrate the exception.

 

The reports are sent when the user clicks on the Submit button, then the ViewReport procedure redirects to an ErrorResult.aspx page. This is a static page that thanks the user, presents a warm and fuzzy image to contrast with exploding volcanoes, and tells them how to return to the application. And life is sweet once again - except now you must solve the problem!

 

The techniques I've presented in this article are only the beginning, providing a foundation for creating bulletproof applications. Or, at least, when they do get shot they can rebound gracefully and still present users with Internet nirvana: a pleasing Web experience.

 

The code referenced in this article is available for download.

 

Don Kiely is senior technology consultant for Information Insights, a business and technology consultancy in Fairbanks, Alaska. E-mail him at mailto:[email protected].

 

Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish