TECHNOLOGIES: Error Handling | Exceptions
Handle Errors in ASP.NET
Detect errors in your ASP.NET application and recover from them easily.
By Wayne S. Freeze
The last thing your users want to see is a strange error message in their browser when running your application. Fortunately, the .NET Framework provides a powerful set of tools you can use to detect and handle errors as they arise in your ASP.NET apps. In this article, you'll learn about how your program can detect different types of errors and how you can recover from them. You'll also learn how to create and handle your own errors.
The .NET Framework introduces a new way to handle errors in your Web application. Each run-time error is represented by a specific object called an exception object. These exception objects contain information specific to the particular error. All exception objects are derived from the System.Exception object, so they have a common set of properties and methods. Many exception objects, however, have additional properties and/or methods unique to that particular object.
Microsoft added the Try statement to the VB language so you can use the exception objects. The Try statement allows you to trap exception objects whenever an error occurs. Because the Try statement follows the rules of structured programming, it's known as a structured exception handler. The Try statement has three main parts. The first part consists of a series of statements that are executed. If an error occurs in these statements, control is turned over to the second part of the Try statement - a series of Catch clauses containing code to be executed if an error occurs. The third part of the Try statement is the Finally clause, which contains a block of code that will be executed before leaving the Try statement.
This code fragment shows how the Try statement can trap any run-time error:
i = i \ 0
Catch ex As Exception
Label1.Text = ex.Message
When the division-by-zero error occurs in the assignment statement, the .NET Framework creates an exception object describing the error. The Catch clause is then triggered and the exception object is stored in the variable declared in the statement. Then within the Catch clause, the message stored in the exception object is copied to the Web page's Label1 control.
In the previous example, I used only one Catch clause, which means the program only knows that an error occurred. Although the Message property contains a text description of the message, you can't rely on the text to determine the nature of the error. Because exception objects generally don't contain error numbers, it's important to identify the exact nature of the error by using the most specific exception object possible. In the previous example that forced a division-by-zero error, the .NET Framework created a System.DivideByZeroException object and copied it to a System.Exception object; this means the exact nature of the error was lost.
By using multiple Catch clauses, where each Catch clause specifies a unique exception object, you can identify the exact error that occurred. When an error occurs, each Catch clause is examined in sequence within the Try statement to determine the first clause that contains an exception class compatible with the exception object created by the error. You must arrange the Catch clauses in descending order starting with the one containing the most specific exception. If you specify a more general exception class first, the statements in its Catch clause will be executed instead of the one associated with the more specific exception object.
Because all exception objects are derived from the System.Exception class, specifying the Exception class in the Catch clause allows you to trap any exception. You always should use the Exception class in the last Catch clause to trap any unexpected errors.
Use Structured Exceptions
Figure 1 shows how to use multiple Catch clauses to determine the exact cause of an error. The first Catch clause handles the InvalidCastException error, which traps any type conversion errors that might have occurred when processing the call to CInt. The second Catch clause handles the case where the user has entered a zero into TextBox2 and caused a division-by-zero error. The third Catch clause specifies the ArithmeticException class to trap any other arithmetic errors that might occur, and the last Catch clause traps any other unspecified errors.
Sub Button3_Click(sender As Object, e As EventArgs)
Dim i As Integer
Dim j As Integer
Dim k As Integer
i = CInt(TextBox1.Text)
j = CInt(TextBox2.Text)
Label1.Text = "No errors occurred"
Catch ex As InvalidCastException
Label1.Text = "Invalid number"
Catch ex As DivideByZeroException
Label1.Text = "Divide by zero"
Catch ex As ArithmeticException
Label1.Text = "Arithmetic error"
Catch ex As Exception
Label1.Text = "An error occurred: " & ex.Message
Figure 1. This code snippet from Errors-1.aspx (see the Download box for details on downloading this code) uses multiple Catch clauses that specify unique exception classes, which can help you determine the exact cause of an error in a Try statement.
As I mentioned earlier, the errors listed in the Catch clauses go from most specific to least specific. In fact, the DivideByZeroException class inherits the ArithmeticException class, which in turn inherits from the Exception class. Other classes such as the OverflowException and NotFiniteNumberException also inherit from the ArithmeticException class. If either of these errors occurs in the example shown in Figure 1, the ArithmeticException clause will be executed because the more specific classes weren't included in Catch clauses.
I didn't use a Finally clause in this example. In general, I avoid using the Finally clause because I don't think it adds a whole lot of value. There are some situations where a Finally clause is warranted, however, because the statements in a Finally clause are guaranteed to execute, whether or not an error occurs. This is important in situations where files or connections need to be closed.
The .NET Framework allows you to define and raise your own exceptions. This is a powerful concept you can use to add .NET error handling to your own application. For instance, you can use it to return an error if the input parameters to a subroutine are illegal or the data being maintained inside a class is inconsistent.
Defining your own exception involves creating a new Class structure that inherits from the System.Exception class (see Figure 2) and defining the appropriate class constructors. At a minimum, your constructors must call the constructor of the base class; beyond that, you are free to add any properties or methods to the class you find useful.
Public Class MyException
Public Value As Integer = 0
Sub New(m As String)
Figure 2. This code from Errors-2.aspx (see the Download box) shows how you can create your own exception class by inheriting from the Exception class.
The Exception class allows you to pass a string containing the text of the message that will be returned in the exception object. The MyException class takes advantage of this capability to provide two constructors - one without parameters, which supplies a default error message, and one that allows the programmer to specify a custom message. The Value property serves as a vehicle to return additional information about the error to the calling program.
Creating an exception is simply a matter of creating a new exception object containing the information you want to return to the user and then using the Throw statement to return the error. Note that your program stops immediately when you throw the exception. If there isn't an exception handler at a higher level, using the Throw statement will trigger an unhandled run-time error.
The TestRoutine subroutine shown in Figure 3 demonstrates how to use both constructors. TestRoutine accepts two parameters - if they are equal, a new instance of the MyException object is created without any parameters. This means the default error message will be returned, and the Throw statement then triggers the error. If the first parameter is less than the second, MyException is created using a custom error message, and the value of the first parameter is passed along as part of the MyException object.
Sub TestRoutine(i As Integer, j As Integer)
Dim k As Integer
Dim ex As MyException
If i=j Then
ex = New MyException()
ElseIf i < j Then
ex = New MyException("The first parameter is invalid")
ex.Value = i
Figure 3. This routine in Errors-2.aspx (see the Download box) uses the Throw statement to trigger an exception and pass information to the calling routine.
Although this example used a custom exception object, there is no reason why you couldn't use the Exception object in place of MyException. The only disadvantage of using Exception in place of MyException is you will be unable to distinguish between the specific exception and any other exceptions that might arise in your Catch clause.
If you decide to use structured exception handling in place of normal flow control statements, you should understand that exception handling is expensive in terms of system resources. For instance, you shouldn't use a Try statement inside a loop where you expect to handle an exception on nearly every loop. Instead, you should check for specific errors before they occur and handle them accordingly. You should reserve exceptions only for those situations where you encounter an error from which you can't recover.
One useful feature of structured error handling is that you can use a Try statement to trap errors from subroutines nested several layers deep. You can deploy this technique in the main body of a program, or you can include it in an event handler to handle errors from nested subroutine calls. Your assignment is to construct a program that calls a set of nested subroutines, with the innermost subroutine causing an exception. Then you should use a Try statement at the top level to catch the error.
The files referenced in this article are available for download.
Wayne S. Freeze is a full-time computer book author with more than a dozen titles to his credit, including Windows Game Programming with Visual Basic and DirectX (Que) and Unlocking OLAP with SQL Server and Excel 2000 (Wiley). He has more than 25 years of experience using all types of computers, from small, embedded microprocessor control systems to large-scale IBM mainframes. Freeze has a master's degree in management information systems as well as degrees in computer science and engineering. Visit his Web site at http://www.JustPC.com or 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.