LANGUAGES: VB .NET
TECHNOLOGIES: Exception Handling
Kill Bugs Dead!
Forget about errors. Exception handling rules the .NET world.
By Don Kiely
The .NET revolution has caused Windows developers to scramble to learn entire new ways of developing applications. Basic concepts must be relearned from the ground up. This is as true for error handling as for any other aspect of development, although it now has the more innocuous sounding name of exception handling (see the sidebar, "Exceptions or Errors?").
The new world of ASP.NET development provides a rich, comprehensive, and often confusing array of tools you can use to handle problems that arise when code runs. Language-based tools managed by .NET's Common Language Runtime (CLR) provide structured exception handling. Unhandled exceptions in code bubble up through various mechanisms for defining declaratively what the user sees when things go wrong. And, because ASP.NET runs in partnership with versions of Internet Information Server (IIS), you can further refine exception handling through IIS mechanisms. Rarely will you utilize only one of these mechanisms, even in a simple application. Most often you'll need to orchestrate them together in a kind of symphony that contributes to the nirvana of a rich user experience.
I'll show you many of the most common mechanisms you'll use in Web applications and how they interact. I won't go into debugging and tracing, although both are closely associated with exception handling. I'll also not cover Simple Object Access Protocol (SOAP) exceptions explicitly, although many of the same mechanisms are useful with SOAP.
You can download this application's samples. I've included two versions: ExceptionsVB, in VB .NET, and ExceptionsCS, in C#, although this article uses snippets from the VB project.
Exception Handling in ASP.NET
You need to deal with four main categories of problems in ASP.NET applications: configuration, HTML parsing, compilation, and runtime problems. Configuration problems arise from the way an application is set up on a server, such as faulty entries in the web.config file or incorrect permissions in IIS. HTML parsing problems arise from flawed code in the Web page itself. Compilation problems are most often syntax-related problems pertaining to the .NET code. Runtime errors cover a gamut of issues when code actually runs.
When you stand back and take an overall view of how exceptions are handled in an ASP.NET application, you'll find four broad levels where you can respond to problems. Where you handle a particular problem depends on the application's requirements, architectural decisions, and the nature of the problem. The exception-handling hierarchy in Figure 1 starts with the lowest level, within individual code procedures, and proceeds to the broadest level, an application-wide, when-all-else-fails mechanism.
Description and Use
Procedure-level exception handling
The CLR's Try...Catch...Finally blocks can catch problems as they occur at your application's lowest level. This is really the only place you can truly handle exceptions, taking some kind of preventive or reactive action perhaps to prevent the user from ever knowing something went wrong.
Page-level exception handling
If a problem occurs in the context of a page, such as malformed HTML or a code exception not handled in the procedure, you can define what ASP.NET should do for this page and this page alone.
Application-level exception handling
The application level is exception handling of last resort - your last chance to do something magical to fix the problem, or at least soften the blow by compassionately notifying the user of what went wrong.
Default ASP.NET exception handling
Every development tool in the history of computing has had a default exception-handling technique. ASP.NET presents the remote user with vaguely attractive but frequently useless information. Unless you hate your users, no application should ever allow such pages to see the phosphorous of a user's monitor.
Figure 1. You can handle problems in an ASP.NET application at many levels.
Look at what ASP.NET provides at each of these levels, starting with the broad and finishing with the most narrow. The basic sample I'll use is shown in Figure 2.
Figure 2. The base sample application defines a few integers and performs both floating-point and integral division. This shows what the user should see when the application runs.
Default handling has two levels. If you do nothing, all errors produce a default page whose content depends on whether the user is local or remote as well as the settings in the web.config file. You also can redirect the user to a custom page if you want them to see something a bit more controlled and consistent with the design of the rest of the Web site. You can read the details about this part of the code in an upcoming issue of the asp.netNOW newsletter (for details, see the "More in asp.netNOW" box).
Application- and Page-Level Exception Handling
When the default ASP.NET exception handling isn't good enough - and alone, it rarely is in production apps - the next step down is application-wide exception handling. This is the first level in the hierarchy where you can handle exceptions programmatically.
At this level, you can respond to the Application object's Error event, which is really the HttpApplication.Error event. This event fires before the default exception handling kicks in, giving you a chance to log errors, send an e-mail to an administrator, or whatever makes sense in the application.
You can handle the Application object's Error event in global.asax (or in the codebehind for global.asax). The sample application includes this procedure, which is about as simple as you can get:
This is equivalent to setting the defaultRedirect in web.config to GenericError.htm as you did earlier. But now you can run whatever code makes sense whenever any unhandled exception occurs in the application. My favorite technique is to page the developer who wrote the offending code, but only between 1 and 4 a.m.
Whether you rely on the ASP.NET default exception handling or you write some code in the Application object's Error event, you can respond to exceptions on a page-by-page basis. This means if you have an overall exception-handling strategy, you can override it for a single page for some requirement-specific reason.
You can accomplish this in two ways. The first is to use the Page class's ErrorPage property. The easiest way to do this is by setting the ErrorPage attribute of the Page directive. If you're using Visual Studio .NET to create applications, each page probably has this directive already. Here's a typical example:
<%@ Page Language="vb" ErrorPage="OrderError.htm"
When an unhandled exception occurs in the page, ASP.NET ignores what you might have specified in web.config or global.asax and it displays OrderError.htm. If an unhandled exception occurs in any other page in the application, global.asax or web.config still kicks in.
The other page-level exception-handling option is the Page object's Error event (see Figure 3). This code even handles the MyBase.Error event and allows you to write whatever code is appropriate to handle the exception.
Private Sub Page_Error( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Dim sMessage As String
sMessage = "" _
" & Request.Url.ToString() & "" _
" _" _
& Server.GetLastError().ToString() _
Figure 3. Here is Page_Error code from the ResultErrorPageEvent.aspx page. .NET provides a lot of information about problems and their causes, which you can put to good use. This example shows both the URL of the problem page as well as the stack trace.
When the user requests this page and the divide-by-zero exception occurs, Figure 4 is displayed. Fundamentally, the event procedure builds a custom string and does a Response.Write to blast it to the page.
Figure 4. For this page, and this page alone, diagnostic information is displayed because of the code included in Page's Error event.
This example demonstrates a small part of a rich set of information you have access to programmatically when things go wrong. In this case, the page uses the Request object's Url property to display the URL the user requested.
More interestingly, it uses the Server object's GetLastError method to get error information and a stack trace where the error occurred. Although this isn't something you'll probably display to a user - there's far too much information that would make a typical user swoon and a hacker drool over - you easily can log the information. Now you can see how powerful the .NET exception-handling techniques can be.
Incidentally, heed the comment in the code just before the call to the Server.ClearError method. This statement essentially tells ASP.NET that this procedure has handled the error, so don't bubble it up any higher to the application-level or default exception-handling behaviors. Without this line, but with a defaultRedirect setting in web.config, the user sees the page specified in defaultRedirect.
Server.GetLastError is a useful method you can use pretty much anywhere you handle exceptions programmatically. But what you always get from this method is an HttpUnhandledException object. You must use this object's InnerException property to get information about the exception that actually caused the problem.
Procedure-Level Exception Handling
One of the general and useful principles of good software development - albeit one that is broken often with good reason - is to handle errors as close to the cause as possible. Handle data errors in stored procedures, handle business logic errors in the middle-tier component, and handle user interface problems on that tier. You might have to notify another tier or component of the problem by raising an error or throwing an exception.
This principle also means you need a mechanism in the code to solve problems. Fortunately, with the coming of .NET, all CLR-compliant languages can use a hip new way of dealing with exceptions: Try...Catch...Finally blocks.
Note to VB developers: You might find reasons to stick with tried-and-true On Error statements, but trust me on this one: It's time to leave these foolish ways behind. Besides, such techniques are deprecated, which in standards-speak means they'll vanish suddenly in some future version of VB.
Structured error handling allows you to write code expecting that problems won't happen, then centralize error-handling code when they do.
The idea of structured exception handling is pretty simple: Wrap any code that could cause a problem - called throwing an exception - in a Try block. To handle the exception, whether that means resolving the problem or passing the buck to another layer of the application, you provide one or more Catch blocks, a sort of specialized case statement based on what type of exception you have. Finally, if there is code that must run whether an exception occurs or not, put that in a Finally block, which is where you close database connections, release file handles, or flush buffers.
In the sample code, ResultHandling.aspx contains a Try...Catch...Finally block that probably is more complex than you're likely to put into most of your procedures, but it'll do for explanation. Figure 5 shows the beginning of the Page's Load event procedure.
Dim x As Integer = 44
Dim y As Integer = 0
Dim z As Double
Dim zi As Double
& "Normal page that never displays a result")
z = x / y
zi = x \ y
lblResult.Text = "When x is " & x.ToString _
& " and y is " & y.ToString _
& ", x / y is " & z.ToString _
& ", and x \ y is " & zi.ToString & "."
Figure 5. Put any code you expect might have problems into a Try block.
Notice that at this point all I've done is identify a few statements likely to cause an exception and slapped a Try block around them. Although the assignment statement writing to the Web form's Label control itself might seem unlikely to throw an exception, I only want that statement to execute if no exceptions occur. Otherwise the user might be confused by a display of bogus results.
The next step in structured error handling is the Catch block. The Catch statement has three basic forms, the simplest being the keyword Catch, a kind of "if-anything-goes-wrong-do-this" situation. Use this form only when you don't care what the actual exception is but need to run some code. More often you'll use a different version of this catchall Catch block, shown in this code from ResultHandling.aspx:
Catch ex As Exception
Dim sMessage As String
sMessage = "" _
" & Request.Url.ToString() & "" _
" & Server.GetLastError().ToString() _"
'Must include the ClearError method, otherwise you'll
'get the default
'ASP.NET error handling, as customized in web.config.
This Catch block wraps some generic code that responds to anything that goes wrong. In this case, it writes a string with the offending URL as well as a stack trace of how the code arrived. This Catch block matches any exception derived, at any level, from the System.Exception class. With the ex variable holding a reference to this object, you can access a wealth of information about the exception, including any nested exceptions, with the InnerException property. Using this object with the other support in ASP.NET for exception handling allows you to create robust, user-friendly applications.
The sample code has multiple Catch blocks, showing how you can respond in different ways to different problems. Most of the time you'll catch different kinds of exceptions - either those defined by the .NET Framework or your own custom exceptions. Multiple Catch blocks act like a specialized Select Case statement. VBers also can use the When keyword to take different actions for the same exception (see the sample code for these examples).
The order of the Catch blocks is important. When an exception is thrown in a Try block, the CLR checks each Catch block in the order it appears after the Try block. As soon as the CLR finds a match - meaning it finds a Catch statement with an exception specified that either matches or is derived from the exception thrown - that Catch block's code is executed. After that, the Finally block code is executed. Execution then continues, either after the exception-handling block, or - if the code throws another exception - up the call stack.
If you need to short-circuit the exception-handling block, you can include an Exit Try statement in either the Try block or the Catch block. This statement causes execution to jump to the end of the exception-handling block, executing the Finally block code if one is included.
Bubble, Bubble, Toil, and Trouble
If the Try...Catch...Finally block has no Catch statement that matches the exception thrown, the exception is thrown to the procedure that called the current procedure. The exception continues to bubble up the call stack until it finds a Catch block that handles the exception or reaches the top of the stack. In the latter case, ASP.NET's default handling kicks in.
One interesting feature of this bubbling behavior is event procedures that don't handle an exception immediately kick in the ASP.NET handling features. This happens because an event procedure sits at the top of the call stack because no other procedure called the event. Much of ASP.NET programming is handling events, which makes carefully designing and implementing ASP.NET's exception-handling features all the more important.
The most important thing to understand is if an exception is handled in code as it should be, the application-level, page-level, and default ASP.NET handling features never kick in. When you're clear on this point, wrapping your head around ASP.NET exception handling becomes much easier.
The sample code in this article is available for download.
Don Kiely is senior information architect 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.
Microsoft, in its C# language specification, describes exceptions as conditions that arise during processing of statements and expressions that prevent the operation from completing normally. They are exceptional events. Technical author Jeffrey Richter describes exceptions as any condition that violates the assumptions built into a procedure. Basically, exceptions are problems.
Exceptions or Errors?
Java or C++ developers might be accustomed to referring to problems arising in code as exceptions. But Visual Basic developers call them errors, and no doubt other common terms are used on different platforms and development tools.
Many people use the terms exception handling and error handling interchangeably, but there are some subtle differences. I'm influenced in this area by author Jeffrey Richter in his book Applied Microsoft .NET Framework Programming (Microsoft Press), specifically its chapter on exceptions.
The term exception implies nothing about how likely something is to happen or how often it happens. Common occurrences, such as reading past the end of a file of unknown size, are just as efficiently handled by exceptions as by other coding techniques.
The term error implies a mistake the developer made - a bug. But this might not be the case because something outside the developer's control could occur, such as running out of disk space or invalid input by a user. The program must deal with these problems, and exception handling is frequently the best way to do it.