Chapter 9: Debugging ASP.NET – ASP.Net Web Developer's Guide

Chapter 9

Debugging ASP.NET

Introduction

Before ASP 3.0, error handling was never a strong suit of ASP. Despite taking great efforts to handle possible error conditions, it is not uncommon to see ASP applications crash and display cryptic error messages. For applications critical to a company’s success, this is a huge embarrassment. You may have seen something like this quite often:

Microsoft VBScript runtime error ‘800a0006’

Overflow

/wad/vote.asp, line 25

Besides handling errors, how many times have you forgotten to remove debugging statements in your application? Often, due to the unrealistic and tight deadlines imposed by management, you end up rushing to deploy your application. In the midst of doing that, a few debugging statements occasionally get left out.

In this chapter, we will look at the new error handling mechanisms available in .NET. We will discuss how to anticipate various kinds of errors and their possible remedies. We will also look at how the new Trace class in ASP.NET allows programmers to trace the flow of ASP.NET applications as well as explore the various capabilities available in the Trace class. Finally, we will show you how to use the Object browser and Class Viewer to look for specific libraries.

Handling Errors

While it is the hope of every programmer to write bug-free programs, it can prove a tasking goal. Bugs in programs can be incredibly frustrating, usually disrupting the programs they infect. Such errors can be classified into these four categories, which we’ll discuss in the following sections:

 Syntax Errors Errors caused by writing codes that do not follow the rules of the language. An example would be a misspelled keyword.

 Compilation Errors Errors that can be detected during the compilation stage. An example would be assigning a big number to an integer variable, causing it to overflow.

 Runtime Errors Errors that happen after the codes are compiled and executed. An example would be a division-by-zero error.

 Logic Errors Errors due to incorrect implementations of algorithms. This is the kind of error that programmers dread most since they are the most difficult to debug.

Syntax Errors

A syntax error is one of the most common errors in programming. This is especially true if you are new to a particular language. Fortunately, syntax errors can be resolved quite easily. In Visual Studio .NET, syntax errors are underlined as shown in Figure 9.1.

Figure 9.1 Syntax Errors Are Underlined

To know the cause of the error, simply position the mouse over the underlined word and the tool tip box will appear. The cause of the error in Figure 9.1 is the misspelled word “Integer.” To correct the error, simply change the word “Interger” to “Integer.”

Compilation Errors

Compilation errors occur when the compiler tries to compile the program and realizes that the program contains codes that may potentially trip up a program. As an illustration, consider the following example, which declares two variables of different data types:

The last line of the code tries to assign the value of an Int32 variable to another variable of type Int16. The risk here is that during runtime, intNum might contain a value that is larger than the range represented by the Short data type. Hence this assignment is not safe (although it will compile and may run without error). This form of assignment where the value of a “wider” data type is assigned to a variable of a “narrower” data type is known as narrowing. The reverse is known as widening. Narrowing is dangerous and could possibly result in runtime errors.

VB.NET supports the Option Strict statement to ensure that only widening conversions are allowed, otherwise it will generate an error message. Modifying our codes, we get:

In this case, our compiler will generate an error message to indicate that such a conversion is not allowed.

The Option Strict On statement must be placed at the first line of your program. Using the Option Strict On also implies Option Explicit On (the Option Explicit statement ensures that variables are declared prior to usage). Thus undeclared variables would also generate error messages.

NOTE

In VB6, the array index can be changed using the Option Base statement. In VB.NET, the Option Base statement is not supported.

Runtime Errors

Runtime errors occur during the time when the application is running and something unexpected occurs. It happens regularly in projects that have very tight deadlines. Programmers stretched to their limits are often satisfied that their program runs. They do not have the time to carefully consider all the different possible scenarios in which their programs may be used, hence the result is often a buggy program. To ensure that an application is as robust and bug-free as possible, it is important to place emphasis on anticipating all the errors that can occur in your program.

Error handling got a new lease on life in the .NET Framework, particularly within the .NET languages. In VB6, error handling was unstructured, done using the primitive On Error statement. In the .NET languages, specifically in VB.NET, error handling can both be structured and unstructured. We will examine the two modes of handling errors in the next section.

Unstructured Error Handling

Using our previous example on narrowing conversions (assuming we use the Option Strict off statement), the following codes will trigger a runtime error:

You should see the error as shown in Figure 9.2.

Figure 9.2 Runtime Error

To prevent the error from happening, VB.NET supports the unstructured On Error statement:

The On Error Resume Next statement ignores any error that happens and continues as though no error has occurred. The error information is contained within the Err object. If an error has occurred, the property Number of the Err object would contain a nonzero value. The Description property will contain the description of the error. Some common errors and their descriptions are shown in Table 9.1.

Table 9.1

Common On Error Statements and Descriptions

On Error Statement Description
On Error Resume Next Specifies that in the event an error occurs, resume execution.
On Error Goto −1 Disables enabled exception in the current subroutine and resets it to Nothing.
On Error Goto 0 Disables error handling.
On Error Goto label Specifies the location to jump to when an error occurs.

The following codes show an extended example outlining use of the On Error statement:

In the preceding example, we examine three errors. The first error will cause the execution to jump to the ErrorHandling block and after the error description has been printed, it resumes execution at the point it was interrupted. The second error will be ignored while the third error will cause the program to fail.

As you can see, unstructured error handling makes your code messy and difficult to debug, and also affects future maintenance. Hence, the recommended way to handle errors is to use structured error handling, which is covered in the next section.

Structured Error Handling

Using unstructured error handling usually results in messy and difficult-to-maintain codes. Rather than placing an On Error statement at the beginning of a block to handle potential errors, .NET supports structured error handling using the Try-Catch-Finally construct. Structured error handling uses the Try-Catch-Finally construct to handle exceptions. The Try-Catch-Finally construct allows developers to actively “catch” different forms of errors and respond to them appropriately. It has the following syntax:

Rewriting our codes using structured error handling, we get:

When executed, the error message printed is:

System.OverflowException: Exception of type System.OverflowException was

thrown. at WebApplication1.WebForm1.Page_Load(Object sender, EventArgs

e) in C:\Documents and Settings\lwm\VSWebCache\LWM\WebApplication1\

WebForm1.aspx.vb:line 31

When the line in the Try block is executed, it generates an exception, which is then caught by the Catch block. The statement in the Catch block prints out the reason for causing that exception.

The previous example doesn’t really do justice to the structured error-handling construct in VB.NET. Consider the following revised example:

Here we have multiple Catch statements. Each Catch statement tries to catch the different kinds of exceptions. If discovered, the exception is evaluated from top to bottom. Once a match is found, the codes within the Catch block are executed. If no match is found, an error message is displayed.

The three exceptions in the preceding list include:

 OutOfMemoryException Thrown when there is not enough memory to continue the execution of a program.

 OverflowException Thrown when an operation results in an overflow condition.

 Exception The base class for exception. This means all unmatched exceptions would be matched here.

When the statement within the Try block generates an exception, the few Catch statements are evaluated in order. First, it compares with the initial Catch block and checks to see if it matches the kind of exception specified in the Catch statement. If it doesn’t, it will compare it with the next, and so on. It only stops when a match is found. In our case, the exception is an overflow exception and hence the second Catch block is matched. If no match is found, an error message will be generated.

Lastly, the Finally block allows you to perform whatever cleaning up operation codes need doing, regardless of whether the exception occurs.

NOTE

You cannot use both structured and unstructured error handling in the same subroutine.

Logic Errors

Logic errors are the most difficult problem to solve! While the previous errors can be taken care of with the help of special language constructs and the compilers, logic errors cannot be resolved so easily. Logic errors result when a piece of code does not work as intended. As an example, consider the following code snippets:

The code is trying to calculate the factorial of a number. Though there are no syntax errors, the code is not producing the expected answer (120). In fact, no result is printed. Only through some tracing and checking is it found that the culprit is actually forgetting to initialize the value of the factorial. To facilitate the debugging of logic errors, ASP.NET provides tracing ability. We will elaborate on the tracing feature available in ASP.NET in the next section.

Page Tracing

During the development stage, you may often need to monitor the value of some variables or functions, especially if they are not giving the correct results. Tracing through the codes is another important debugging method to make sure your codes flow in the intended manner.

ASP.NET provides tracing ability to easily map the flow of an application. In ASP, debugging is a painful process. You must often use the Response. Write() method to output the values of variables:

How often have you forgotten to remove the debugging statements after you have tracked the error and deployed your application?

Using the Trace Class

ASP.NET includes the Trace class to help trace the flow of an application. Instead of using the Response object for debugging, we now get:

To activate the trace, the page directive needs to have a Trace attribute with its value set to “true,” as shown in Figure 9.3. By just changing the value of the Trace attribute, we can turn tracing on or off. When the application is ready for deployment, simply set the Trace attribute to the value “false.” There is no need to remove the Trace statements in your application.

Figure 9.3 Enabling Tracing

When the ASP.NET application is run, the following output is shown (Figure 9.4). Table 9.2 contains the following sections of the Trace page (not all are shown in Figure 9.4).

Table 9.2

Sections in a Trace Page

Sections Description
Request Details Describes information pertaining to the request (e.g., SessionID, Encoding, and time of request).
Trace Information Contains detailed information about the application currently running. Trace information is displayed in this section.
Control Tree Displays information about controls used in a page and the size of the Viewstate hidden field.
Cookies Collection Displays the cookie set by the page and its value.
Headers Collection Displays HTTP header information like content length and user agent.
Forms Collection Displays the name of controls in a page and its value.
Server Variables Displays the environment variables on the server side.

Figure 9.4 Displaying the Trace Information

Notice that our Trace message is written under the “Trace Information” section.

The Trace class contains the following members (Table 9.3 and Table 9.4).

Table 9.3

Properties in the Trace Class

Property Description
IsEnabled Indicates whether tracing is enabled for the current request.
TraceMode Sets the trace mode: sortByCategory or sortByTime.

Table 9.4

Methods in the Trace Class

Methods() Description
Warn Writes the trace information in red.
Write Writes the trace information.

For example, the Warn() method of the Trace class causes the Trace information to be printed in red as shown in Figure 9.5 (all the nonshaded lines you see are displayed in red).

Figure 9.5 Using the Warn() Method to Display Trace Information

NOTE

Turning tracing on and off is just a matter of modifying the value of the Trace attribute in the page directive.

Sorting the Trace Information

Inserting multiple Trace statements in an application can sometimes be messy. It is useful if the Trace information is classified into different categories to make tracing easier. The Trace class allows us to create different debugging categories and sort the Trace information based on these categories. The following example shows how to group the different categories of Trace information:

The output of the preceding code is shown in Figure 9.6.

Figure 9.6 Sorting by Category (All Lines in the “Counter” Category Are Displayed in Red)

Let’s dissect the preceding codes:

Trace.TraceMode = TraceMode.SortByCategory

The TraceMode property sets the modes supported by the trace:

 SortByCategory Trace information is sorted by category.

 SortByTime Trace information is displayed in the sequence of execution.

Since we are sorting the Trace mode by category, notice that Figure 9.6 shows the messages are sorted by category.

Trace.Warn(“counter”, “value of i is ” & i)

The Warn method displays the message in red and notes that this method is overloaded. In this case, we pass in two arguments. The first goes into the Category and the second is for the Message.

Trace.Write(“Factorial”, “value of factorial is” & factorial)

The Write() method of the Trace object is also overloaded, just like the Warn() method. This time around, we write the message into the “Factorial” category.

Besides using the Trace class to set the Trace mode, you can also use the Page directive to set the Trace mode:

Writing the Trace Information to the Application Log

Although displaying the Trace information within the page is useful, sometimes you need to trace the page while users are utilizing your application. In such cases, the user should not see the Trace information.ASP.NET provides a mean for the Trace information to be written to a log file. The following example shows how the Trace information is written to the application log:

The System.Diagnostics namespace provides the class to debug our application. In particular, we used the EventLog component to help us write messages to the application log. To view the message, use the Event Viewer. Our message is shown in Figure 9.7.

Figure 9.7 Writing to the Application Log

To see the message details, double-click the event item. The detailed message is shown in Figure 9.8.

Figure 9.8 Details of the Message

Application Tracing

This last section discusses page tracing which maps the flow within a page. ASP.NET also supports tracing at the application level. Application-level tracing is set in the web.config file, under the trace section:

To enable an application-level trace, set the following values shown in Table 9.5.

Table 9.5

Attributes of the Trace Element

Attribute Value Description
enabled true Enables or disables application-level tracing.
requestLimit 10 Sets the maximum number of requests to trace.
pageOutput false Displays the trace at the end of the page.
traceMode SortByTime Trace information sort order.
localOnly true Sets the ability to see trace viewer on a nonlocal machine.

When the application is loaded, the Trace information does not appear on the page. To view the Trace information, we need to use the Trace viewer (trace.axd) shown in Figure 9.9.

Figure 9.9 Application Level Tracing

Figure 9.9 shows the Trace information of the last ten requests to the application. To view the detailed information of each request, click the View Details link of each row.

NOTE

If the trace is set to “true” in the web.config file and set to “false” in the page directive, tracing is disabled.

Using Visual Studio .NET Debugging Tools

Visual Studio .NET contains a rich set of debugging tools to help developers debug their applications. In this section, we look at some of the tools available.

Setting Breakpoints

Besides using the Trace class to trace the value of variables in your application, another method is to set breakpoints in your application. Visual Studio .NET allows you to do this so you can examine and trace the flow of your application during runtime. Figure 9.10 shows a breakpoint (indicated by a dot, which shows up as red on the screen).

Figure 9.10 Setting a Breakpoint (Designated by Red Dot)

NOTE

Visual Studio 6 developers should be familiar with setting breakpoints in the IDE.

When the application is run, the execution would stop at the breakpoint. Three options are available:

 Step Into The execution would then move into the function named Factorial. Each step would execute a line (by pressing F11).

 Step Over The execution would execute the function (without stepping through the codes within the function) and treat the function as a single line. This is achieved by pressing F10.

 Step Out This option is available if the current execution point is in the function and you want to execute the rest of the codes in the function without stepping through them. It then returns to the calling function.

Besides tracing the flow, you can also examine the values of variables during a breakpoint. There are two ways to examine the values of variables:

 Tool tip help Position the cursor over the variable you want to examine. The value will be displayed in a tool tip dialog box.

 Watch window Examine the value of variables by using the Watch window (activated by choosing Debug | Windows | Watch).

Enabling and Disabling Debug Mode

By default, your ASP.NET application is in debug mode. The <compilation> element in the web.config file controls this:

<compilation defaultLanguage=“vb” debug=“true” />

During compilation, debugging symbols (.pdb information) are inserted into the compiled page. As a result, the application will run slower than without the debugging symbols. As such, remember to set the debug attribute to false when you deploy your application.

Viewing Definitions Using the Object Browser

One of the key aspects of successful .NET programming is the ability to use the appropriate class libraries provided by the framework. While the MSDN documentation is a good place to find out about the class libraries, a better option would be to use the Object Browser provided by the .NET SDK. To launch the Object Browser in Visual Studio .NET, press Ctrl+Alt+J.

Figure 9.11 shows the Object Browser with the System assembly and its associated namespaces exposed. Members of the class UriFormationException are shown on the right window, while the bottom window shows the description of the selected member.

Figure 9.11 Using the Object Browser

Using the Class Viewer

Besides employing the Object Browser to view the various class libraries available, you can also use the Class Viewer. To launch the Class Viewer, type WinCV at the command prompt. Figure 9.12 shows the Class Viewer.

Figure 9.12 Using the Class Viewer

The Class Viewer allows you to type in the keyword to search and display all matching instances of the search word. For example, Figure 9.12 shows the search result for “overflowexception.” It also displays the corresponding namespace and the members of the selected class.

Summary

Error handling is an important aspect of software development. Good robust applications anticipate various errors and take an active role in resolving them without crashing the program. In this chapter, we have seen two distinctive methods of error handling—structured and unstructured. While the unstructured error handling mechanism continues to be supported in .NET, it is recommended that programmers make the switch to the structured error handling mechanism using the Try-Catch-Finally statement. Besides handling errors, the new tracing capability found in .NET makes the life of a programmer much easier. No longer do you have to insert Response. Write statements into your application, you can now trace your application using the Trace class. Removing the Trace statements during deployment is simply a matter of setting an attribute. Finally, Visual Studio .NET allows you to set breakpoints in your application so that the flow of variables and codes can be examined during runtime.

Solutions Fast Track

Handling Errors

 There are four main categories of programming errors: syntax, compilation, runtime, and logic errors.

 Visual Studio .NET IDE provides help for detecting syntax errors.

 Runtime errors can be handled using structured and unstructured error handling mechanisms.

 Structured handling using the Try-Catch-Finally statement is the recommended mode for handling runtime errors in .NET.

Page Tracing

 The Trace class provides tracing capability.

 Turning tracing on and off is easy.

 Trace information can be grouped into multiple categories for easier viewing and it can be written into log files, viewable using the Event Viewer.

 Tracing can be done at the page level or at the application level.

Using Visual Studio .NET Debugging Tools

 Programmers can use the Visual Studio .NET IDE to set breakpoints in their application.

 Breakpoints allow you to examine variables and trace the execution flow of your application.

 The Object Browser and Class Viewer provide quick reference to the various class libraries.

Frequently Asked Questions

The following Frequently Asked Questions, answered by the authors of this book, are designed to both measure your understanding of the concepts presented in this chapter and to assist you with real-life implementation of these concepts. To have your questions about this chapter answered by the author, browse to www.syngress.com/solutions and click on the “Ask the Author” form.

Q: Is the Try-Catch-Finally block available in C# as well?

A: Yes, the Try-Catch-Finally block is available in both VB.NET and C#.

Q: Can I use both structured and unstructured error handling within a function/subroutine?

A: No, you cannot use both error handling mechanisms at the same time. It is recommended you use structured error handling in .NET.

Q: When I try to run my ASP.NET application in VS.NET, I encounter this error message “Error while trying to run project: Unable to start debugging on the Web server. The project is not configured to be debugged.” Why does this occur?

A: This is caused by the setting of the debug attribute within the <compilation> element. During development stage, set the value of the debug attribute to “true.” Remember, however, to set this attribute to “false” when you are ready to deploy your application.

Q: I noticed during tracing that the Session ID for my application changes when I refresh my page or when I do a postback. Why is this happening?

A: For performance reasons, the .NET Framework does not maintain state between the Web server and the Web browser automatically, hence the Session ID is always different between submissions. However, when the Session object is used or when the Session_OnStart() event is added to the global.asax file, the Session ID would be maintained between postbacks.