WHAT’S IN THIS CHAPTER?
WROX.COM CODE DOWNLOADS FOR THIS CHAPTER
Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.
Your code always runs exactly as you wrote it, and you will never get it right the first time. So, expect to spend about 30 percent of your time debugging, and to be a successful debugger, you should learn to use the available tools effectively. Visual Studio has upped the ante, giving you a host of features that greatly improve your debugging experience. Having so many features, however, can be overwhelming at first. This chapter breaks down all the techniques available to you, one at a time, while presenting a holistic view of Visual Studio, the Common Language Runtime (CLR), and the Base Class Library (BCL).
Additionally, because debugging is more than stepping through code, this chapter discusses efficient error and exception handling, tracing and logging, and cross-language (C#, Visual Basic, client-side JavaScript, XSLT, and SQL Stored Procedure) debugging.
Visual Studio has always done a good job of warning you of potential errors at design time. Syntax notifications or squiggles underline code that won’t compile or that might cause an error before you have compiled the project. A new error notification pops up when an exception occurs during a debugging session and recommends a course of action that prevents the exception. At every step, Visual Studio tries to be smarter, anticipating your needs and catching common mistakes.
Rico Mariani, the Chief Architect of Visual Studio, has used the term “The Pit of Success” to describe the experience Microsoft wants you to have with Visual Studio. When Microsoft designed these features, it wanted the customer to simply fall into winning practices. The company tried to achieve this by making it more difficult for you to write buggy code or make common mistakes. Microsoft’s developers put a great deal of thought into building APIs that point you in the right direction.
Both the Visual Basic and C# editors show squiggles and tooltips for many syntax errors well before compilation, as illustrated in Figure 29-1.
Syntax notifications aren’t just for CLR programming languages; Visual Studio works just as well through the XML Editor and includes capabilities like the following:
Figure 29-2 shows a detailed tooltip indicating that the element <junk> does not have any business being in the web.config file. The editor knows this because of a combination of the XSD validation support in the XML Editor and the addition of schemas for configuration files such as web.config. This change is welcome for anyone who, when manually editing a web.config file, has wondered whether he guessed the right elements.
The ASPX/HTML Editor benefits from these capabilities as well; for example, Figure 29-3 shows a warning that the <badElement/> element is not available in the active schema. Code that appears in <script runat="Server"/> blocks in ASP.NET pages is also parsed and marked with squiggles. This makes including code in your pages considerably easier. Notice also that the ASP.NET page in Figure 29-3 has an XHTML DOCTYPE declaration on the first line, and the HTML element has a default XHTML namespace. This HTML page is treated as XML because XHTML has been targeted.
The Visual Basic Editor takes assistance to the next level with a smart tag like the pull-down/button that appears when you hover your mouse cursor over a squiggle. A very nicely rendered modeless window appears with your code in a box along with some suggested changes to make your code compile. Figure 29-4 shows a recommendation to insert a missing End If; making the correction is simple — just click Insert the missing 'End If'.
All these design-time features exist to help you ensure better code while it’s being written, before it’s been compiled and run. Two related features — the Immediate and Command windows — help you run arbitrary code within the development environment as well as organize the tasks still to be performed.
The Immediate window lets you run arbitrary bits of code in design mode without compiling your application. You can evaluate code at design time or while you are debugging. It can be a great way to test a line of code or a static method quickly. The Immediate mode of this window is used primarily for debugging.
Access the Immediate window from Debug ⇒ Windows ⇒ Immediate. To evaluate a variable or run a method, simply click in the Immediate window and type a question mark (?) followed by the expression, variable, or method you want to evaluate.
The Immediate window can also be switched into the Command window by prefacing commands with a greater-than sign (>). When you enter a greater-than sign in the Immediate or Command window, an IntelliSense drop-down appears exposing the complete Visual Studio object model as well as any macros that you may have recorded. Command mode of this window is used for executing Visual Studio commands without using the menus. You can also execute commands that may not have a menu item.
If you type >alias into the Command window, you receive a complete list of all current aliases and their definitions. Some useful command aliases include the following:
The Task List in Visual Studio is more useful than you might think. People who have not given it much attention are missing out on a great feature. The Task list supports two views: User Tasks and Comments.
The User Tasks view enables you to add and modify tasks, which can include anything from “Remember to Test” to “Buy Milk.” These tasks are stored in the .suo (solution user options) file that is a parallel partner to the .sln files.
The Comments view shows text from the comments in your code where those lines are prefixed with a specific token. Visual Studio comes configured to look for the TODO: token, but you can add your own in Tools ⇒ Options ⇒ Environment ⇒ Task List.
In Figure 29-5, the comment token HACK has been added in the Options dialog box. A comment appears in the source with HACK: preceding it, so that comment line automatically appears in the Task List in the docked window at the bottom of Visual Studio. The three circles in Figure 29-5 illustrate the connection between the word HACK added to the Options dialog box and its subsequent appearance in the source code and Task List. You and your team can add as many of these tokens as you want.
Tracing is a way to monitor the execution of your ASP.NET application. You can record exception details and program flow in a way that does not affect the program’s output. In classic ASP, tracing and debugging facilities were nearly nonexistent, forcing developers to use “got here” debugging in the form of many Response.Write statements that litter the resulting HTML with informal trace statements announcing to the programmer that the program “got here” and “got there” with each new line executed. This kind of intrusive tracing was very inconvenient to clean up, and many programmers ended up creating their own informal trace libraries to get around these classic ASP limitations.
In ASP.NET, there is rich support for tracing. The destination for trace output can be configured with TraceListeners, such as the EventLogTraceListener. Configuration of TraceListeners is covered later in this section. ASP.NET allows for trace forwarding between the ASP.NET page-specific Trace class and the standard Base Class Library’s (BCL) System.Diagnostics.Trace used by non-web developers. Additionally, the resolution of the timing output by ASP.NET tracing has a precision of 18 digits for highly accurate profiling.
Multiple things are named Trace in the whole of the .NET Framework, so it may appear that tracing isn’t unified between web and non-web applications. Don’t be confused because there is a class called System .Diagnostics.Trace and there is also a public property on System.Web.UI.Page called Trace. The Trace property on the Page class gives you access to the System.Web.TraceContext and the ASP.NET-specific tracing mechanism. The TraceContext class collects all the details and timing of a web request. It contains a number of methods, but the one you will use the most is Write. It also includes Warn, which simply calls Write(), and also ensures that the output generated by Warn is colored red.
If you’re writing an ASP.NET application that has no supporting components or other assemblies that may be used in a non-web context, you can usually get a great deal of utility using only the ASP.NET TraceContext. However, ASP.NET support tracing is different from the rest of the Base Class Library’s tracing. You explore ASP.NET’s tracing facilities first, and then learn how to bridge the gap and see some features that make debugging even easier.
You can enable ASP.NET tracing on a page-by-page basis by adding Trace="true" to the Page directive in any ASP.NET page:
<%@ Page Language="C#" Inherits="System.Web.UI.Page" Trace="true" %>
Additionally, you can add the TraceMode attribute that sets SortByCategory or the default, SortByTime. You might include a number of categories, one per subsystem, and use SortByCategory to group them, or you might use SortByTime to see the methods that take up the most CPU time for your application. You can enable tracing programmatically as well, using the Trace.IsEnabled property. The capability to enable tracing programmatically means you can enable tracing via a query string, cookie, or IP address; it’s up to you.
Alternatively, you can enable tracing for the entire application by adding tracing settings in the web.config file. In the following example, pageOutput="false" and requestLimit="20" are used, so trace information is stored for 20 requests, but not displayed on the page:
<configuration>
<system.web>
<trace enabled="true" pageOutput="false" requestLimit="20"
traceMode="SortByTime" localOnly="true" />
</system.web>
</configuration>
The page-level settings take precedence over settings in the web.config file, so if enabled="false" is set in the web.config file but trace="true" is set on the page, tracing occurs.
You can view tracing for multiple page requests at the application level by requesting a special page handler called trace.axd. Note that trace.axd doesn’t actually exist; it is provided by System.Web .Handlers.TraceHandler, a special IHttpHandler to which trace.axd is bound. When ASP.NET detects an HTTP request for trace.axd, that request is handled by the TraceHandler rather than by a page.
Create a website and a page, and in the Page_Load event, call Trace.Write(). Enable tracing in the web.config file as shown in Listing 29-1.
LISTING 29-1: Tracing using Page.Trace
<configuration>
<system.web>
<trace enabled="true" pageOutput="true" />
</system.web>
</configuration>
VB
Protected Sub Page_Load(ByVal sender As Object,
ByVal e As System.EventArgs)
Handles Me.Load'All on one line!
Trace.Write("This message is from the START OF the Page_Load method!")
End Sub
C#
protected void Page_Load(object sender, EventArgs e)
{
Trace.Write("This message is from the START of the Page_Load method!");
}
Open the page in the browser a few times and notice that, although this page doesn’t create any HTML to speak of, a great deal of trace information is presented in the browser, as shown in Figure 29-6, because the setting is pageOutput="true".
The message from Trace.Write appears after Begin Load and before End Load — it’s right in the middle of the Page_Load method where you put it. The page was automatically JIT-compiled as you ran it, and that initial performance hit is over. Now that it’s been compiled into native code, a subsequent run of this same page, performed by clicking Refresh in the browser, took only 0.000167 seconds on my laptop because the page had already compiled. Collecting this kind of very valuable performance timing data between trace statements is very easy and extremely useful.
Eleven different sections of tracing information provide a great deal of detail and specific insight into the ASP.NET page-rendering process, as described in Table 29-1.
SECTION | DESCRIPTION |
Request Details | Includes the ASP.NET session ID, the character encoding of the request and response, and the HTTP conversation’s returned status code. Be aware of the request and response encoding, especially if you are using any non-Latin character sets. If you are returning languages other than English, you’ll want your encoding to be UTF-8. Fortunately, that is the default. |
Trace Information | Includes all the Trace.Write methods called during the lifetime of the HTTP request and a great deal of information about timing. This is probably the most useful section for debugging. The timing information located here is valuable when profiling and searching for methods in your application that take too long to execute. |
Control Tree | Presents an HTML representation of the ASP.NET Control Tree. Shows each control’s unique ID, runtime type, the number of bytes it took to be rendered, and the bytes it requires in ViewState and ControlState. Do not undervalue the usefulness of these two sections, particularly of the three columns showing the weight of each control. The weight of the control indicates the number of bytes occupied in ViewState and/or ControlState by that particular control. Be aware of the number of bytes that each of your controls uses, especially if you write your own custom controls, because you want your controls to return as few bytes as possible to keep overall page weight down. |
Session State | Lists all the keys for a particular user’s session, their types, and their values. Shows only the current user’s session state. |
Application State | Lists all the keys in the current application’s Application object and their types and values. |
Request Cookies | Lists all the cookies passed in during the page’s request. |
Response Cookies | Lists all the cookies that were passed back during the page’s response. |
Headers Collection | Shows all the headers that might be passed in during the request from the browser, including Accept-Encoding, indicating whether the browser supports compressed HTTP responses; Accept-Languages, a list of ISO language codes that indicate the order of the user’s language preferences; and User-Agent, the identifying string for the user’s browser. The string also contains information about the user’s operating system and the version or versions of the .NET Framework he is running (on IE). |
Form Collection | Displays a complete dump of the Form collection and all its keys and values. |
Querystring Collection | Displays a dump of the Querystring collection and all its contained keys and values. |
Server Variables | A complete dump of name-value pairs of everything that the web server knows about the application and the requesting browser. |
Page output of tracing shows only the data collected for the current page request. However, when visiting http://localhost/yoursite/trace.axd you will see detailed data collected for all requests to the site thus far. If you’re using the built-in ASP.NET Development Server, remove the current page from the URL and replace it with trace.axd. Do not change the automatically selected port or path.
Again, trace.axd is an internal handler, not a real page. When it’s requested from a local browser, as shown in Figure 29-7, it displays all tracing information for all requests up to a preset limit.
Figure 29-7 shows that five requests have been made to this application and the right side of the header indicates “Remaining: 5.” That means that there are five more requests remaining before tracing stops for this application. After the final request, tracing data is not saved until an application recycle or until you click “Clear current trace” from the trace.axd page. The request limit can be raised in the web.config file at the expense of memory:
<trace requestLimit="100" pageOutput="true" enabled="true"/>
The maximum request limit value is 10000. If you try to use any greater value, ASP.NET uses 10000 anyway and gives you no error. However, you can add a property called mostRecent to the trace section in ASP.NET. When set to true, it shows the most recent requests that are stored in the trace log up to the request limit — instead of showing tracing in the order it occurs (the default) — without using up a lot of memory. Setting mostRecent to true causes memory to be used only for the trace information it stores and automatically throws away tracing information over the requestLimit.
Clicking View Details from Trace.axd on any of these requests takes you to a request-specific page with the same details shown in Figure 29-6.
The tracing facilities of ASP.NET are very powerful and can stand alone. However, you saw a previous mention of System.Diagnostics.Trace, the tracing framework in the Base Class Library that is not web-specific and that receives consistent and complete tracing information when an ASP.NET application calls a non-web-aware component. This can be confusing. Which should you use?
System.Diagnostics.Trace is the core .NET Framework tracing library. Along with System.Diagnostics.Debug, this class provides flexible, non-invasive tracing and debug output for any application. However, as mentioned earlier, rich tracing is built into the System.Web namespace. As a web developer, you will find yourself using ASP.NET’s tracing facilities. You may need to have ASP.NET-specific tracing forwarded to the base framework’s System.Diagnostics.Trace, or more likely, you will want to have your non-web-aware components output their trace calls to ASP.NET so you can take advantage of trace.axd and other ASP.NET-specific features.
Additionally, some confusion surrounds Trace.Write and Debug.Write functions. Look at the source code for Debug.Write, and you see something like this:
[Conditional("DEBUG")]
public static void Write(string message)
{
TraceInternal.Write(message);
}
Notice that Debug.Write calls a function named TraceInternal.Write, which has a conditional attribute indicating that Debug.Write is compiled only if the debug preprocessor directive was set. In other words, you can put as many calls to Debug.Write as you want in your application without affecting your performance when you compile in Release mode. This enables you to be as verbose as you want during the debugging phase of development.
TraceInternal cycles through all attached trace listeners, meaning all classes that derive from the TraceListener base class and are configured in that application’s configuration file. The default TraceListener lives in the aptly named DefaultTraceListener class and calls the Win32 API OutputDebugString. OutputDebugString sends your string into the abyss and, if a debugger is listening, it is displayed. If no debugger is listening, OutputDebugString does nothing. Everyone knows the debugger listens for output from OutputDebugString, so this can be a very effective way to listen in on debug versions of your application.
Now, if you look at the source code for Trace.Write (that’s TRACE not DEBUG), you see something like this:
[Conditional("TRACE")]
public static void Write(string message)
{
TraceInternal.Write(message);
}
The only difference between Debug.Write and Trace.Write given these two source snippets is the conditional attribute indicating the preprocessor directive TRACE. You can conditionally compile your assemblies to include tracing statements, debug statements, both, or neither. Most people keep TRACE defined even for release builds and use the configuration file to turn tracing on and off. More than likely, the benefits you gain from making tracing available to your users far outweigh any performance issues that might arise.
Because Trace.Write calls the DefaultTraceListener just like Debug.Write, you can use any debugger to tap into tracing information. So, what’s the difference?
When designing your application, think about your deployment model. Are you going to ship debug builds or release builds? Do you want a way for end users or systems engineers to debug your application using log files or the event viewer? Are there things you want only the developer to see?
Typically, you want to use tracing and Trace.Write for any formal information that could be useful in debugging your application in a production environment. Trace.Write gives you everything that Debug.Write does, except it uses the TRACE preprocessor directive and is not affected by debug or release builds.
This means you have four possibilities for builds: Debug On, Trace On, Both On, or Neither On. You choose what is right for you. Typically, use Both On for debug builds and Trace On for production builds. You can specify these conditional attributes in the property pages or the command line of the compiler, as well as with the C# #define keyword or #CONST keyword for Visual Basic.
You often find existing ASP.NET applications that have been highly instrumented and make extensive use of the ASP.NET TraceContext class. ASP.NET includes an attribute to the web.config <trace> element that enables you to route messages emitted by ASP.NET tracing to System.Diagnostics.Trace: writeToDiagnosticsTrace:
<trace writeToDiagnosticsTrace="true" pageOutput="true" enabled="true"/>
When you set writeToDiagnosticsTrace to true, all calls to System.Web.UI.Page.Trace.Write (the ASP.NET TraceContext) also go to System.Diagnostics.Trace.Write, enabling you to use all the standard TraceListeners and tracing options that are covered later in this chapter. The simple writeToDiagnoticsTrace setting connects the ASP.NET tracing functionality with the rest of the Base Class Library. I use this feature when I am deep in debugging my pages, and it is easily turned off using this configuration switch. I believe that more information is better than less, but you may find the exact page event information too verbose. Try it and form your own opinion.
Output from System.Diagnostics.Trace methods is routable by a TraceListener to a text file, to ASP.NET, to an external monitoring system, even to a database. This powerful facility is a woefully underused tool in many ASP.NET developers’ tool belts. In the early days of ASP.NET, some component developers who knew their components were being used within ASP.NET would introduce a direct reference to System.Web and call HttpContext.Current.Trace. They did this so that their tracing information would appear in the developer-friendly ASP.NET format. All components called within the context of an HttpRequest automatically receive access to that request’s current context, enabling the components to talk directly to the request and retrieve cookies or collect information about the user.
However, assuming an HttpContext will always be available is dangerous for a number of reasons. First, you are making a big assumption when you declare that your component can be used only within the context of an HttpRequest. Notice that this is said within the context of a request, not within the context of an application. If you access HttpContext.Current even from within the Application_Start, you will be surprised to find that HttpContext.Current is null. Second, marrying your component’s functionality to HttpContext makes using your application in any non-web context tricky if not impossible, and unit testing becomes particularly difficult.
If you have a component that is being used by a web page, but it also needs to be unit tested outside of web context or must be called from any other context, do not call HttpContext.Current.Trace. Instead, use the standard System.Diagnostics.Trace and redirect output to the ASP.NET tracing facilities using the WebPageTraceListener described in the next section. Using the standard trace mechanism means your component can be used in any context, web or otherwise. You will still be able to view the component’s trace output with a TraceListener.
The framework comes with a number of very useful TraceListeners; you can add them programmatically or via a .config file. For example, you can programmatically add a TraceListener log to a file, as shown in Listing 29-2. These snippets required the System.Diagnostics and System.IO namespaces.
LISTING 29-2: Configuring TraceListeners
VB
Dim myTextListener As New
TextWriterTraceListener(File.Create("c:\myListener.log"))
Trace.Listeners.Add(myTextListener)
C#
TextWriterTraceListener myTextListener = new
TextWriterTraceListener(File.Create(@"c:\myListener.log"));
Trace.Listeners.Add(myTextListener);
You can do the same thing declaratively in the web.config file via an add element that passes in the type of TraceListener to use, along with any initializing data it might need. TraceListeners already configured in machine.config or a parent web.config file can also be removed using the remove tag, along with their name:
<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="myListener"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="c:\myListener.log" />
<remove name="Default" />
</listeners>
</trace>
</system.diagnostics>
</configuration>
TraceListeners, such as TextWriterTraceListener, that access a resource (such as a file, event log, or database) require that the ASP.NET worker process be run as a user who has sufficient access. To write to c:\foo\example.log, for example, the ASP.NET worker process requires explicit write access in the access control list (ACL) of that file.
Notice the preceding example also optionally removes the default TraceListener. If you write your own TraceListener, you must provide a fully qualified assembly name in the type attribute.
The ASP.NET WebPageTraceListener derives from System.Diagnostics.TraceListener and automatically forwards tracing information from any component calls to System.Diagnostics.Trace.Write. This enables you to write your components using the most generic trace provider and to see its tracing output in the context of your ASP.NET application.
The WebPageTraceListener is added to the web.config file as shown in the following example. Note that this code uses the fully qualified assembly name for System.Web:
<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="webListener"
type="System.Web.WebPageTraceListener, System.Web, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</listeners>
</trace>
</system.diagnostics>
<system.web>
<trace enabled="true" pageOutput="false" localOnly="true" />
</system.web>
</configuration>
Figure 29-8 shows output from a call to System.Diagnostics.Trace.Write from a referenced library. It appears within ASP.NET’s page tracing. The line generated from the referenced library is circled in this figure.
Tracing information can also be sent to the event log using the EventLogTraceListener. Doing this can be a little tricky because ASP.NET requires explicit write access to the event log:
<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="4">
<listeners>
<add name="EventLogTraceListener"
type="System.Diagnostics.EventLogTraceListener"
initializeData="Wrox"/>
</listeners>
</trace>
</system.diagnostics>
</configuration>
Notice that "Wrox" is passed in as a string to the initializeData attribute as the TraceListener is added. The string "Wrox" appears as the application or source for this event. This works fine when debugging your application; most likely, the debugging user has the appropriate access. However, when your application is deployed, it will probably run under a less privileged account, so you must give explicit write access to a registry key such as HKLM\System\CurrentControlSet\Services\EventLog\Application\Wrox, where "Wrox" is the same string passed in to initializeData. Remember that registry keys have ACLs (access control lists) just as files do. Use RegEdit.exe to change the permissions on a registry key by right-clicking the key and selecting Properties, and setting the ACL just like you would for a file.
Be careful when using the EventLogTraceListener because your event log can fill up fairly quickly if you have a particularly chatty application. Figure 29-9 shows a sample tracing output in the event log.
The .NET Framework 4 includes two additional TraceListeners in addition to the WebPageTraceListener:
One of the interesting things to note about the XML created by the XmlWriterTraceListener — it is not well-formed XML! Specifically, it doesn’t have a root node; it’s just a collection of peer nodes, as shown in the following code. This may seem like it goes against many of the ideas you have been told about XML, but think of each event as a document. Each stands alone and can be consumed alone. They just happen to be next to each other in one file. Certainly, the absence of an ultimate closing tag cleverly dodges the issue of well-formedness and allows easy appending to a file.
<E2ETraceEvent xmlns=\"http://schemas.microsoft.com/2012/06/E2ETraceEvent\">
<System xmlns=\"http://schemas.microsoft.com/2012/06/windows/eventlog/system\">
<EventID>0</EventID>
<Type>3</Type>
<SubType Name="Information">0</SubType>
<Level>8</Level>
<TimeCreated SystemTime="2012-11-05T12:43:44.4234234Z">
<Source Name="WroxChapter29.exe"/>
<Correlation ActivityID="{00000000-0000-0000-0000-000000000000>
<Execution ProcessName="WroxChapter29.exe" ProcessID="4234" ThreadID="1"/>
<Channel/>
<Computer>SCOTTPC</Computer>
</System>
<ApplicationData>Your Text Here</ApplicationData>
</E2ETraceEvent>
<E2ETraceEvent xmlns=\"http://schemas.microsoft.com/2012/06/E2ETraceEvent\">
<System xmlns=\"http://schemas.microsoft.com/2012/06/windows/eventlog/system\">
<EventID>0</EventID>
<Type>3</Type>
. . . the XML continues . . .
The “E2E” in E2ETraceEvent stands for end-to-end. Notice that it includes information such as your computer name and a “correlation id.”
Ever since the .NET Framework 3.5, there has been one additional TraceListener added to the list: the IisTraceListener. Much like the WebPageTraceListener bridges Diagnostics Tracing with ASP.NET tracing, the IisTraceListener bridges the tracing mechanism of ASP.NET with IIS 7.0. This listener lets you raise events to the IIS 7.0 infrastructure.
Recompiling your application just because you want to change tracing characteristics is often not convenient. Sometimes you may want to change your configuration file to add and remove TraceListeners. At other times, you may want to change a configuration parameter or “flip a switch” to adjust the amount of detail the tracing produces. That is where Switch comes in. Switch is an abstract base class that supports a series of diagnostic switches that you can control by using the application’s configuration file.
To use a BooleanSwitch, create an instance and pass in the switch name that appears in the application’s config file (see Listing 29-3).
LISTING 29-3: Using diagnostic switches
<configuration>
<system.diagnostics>
<switches>
<add name="ImportantSwitch" value="1" /> <!-- This is for the BooleanSwitch -->
<add name="LevelSwitch" value="3" /> <!-- This is for the TraceSwitch -->
<add name="SourceSwitch" value="4" /> <!-- This is for the SourceSwitch -->
</switches>
</system.diagnostics>
</configuration>
Switches can be used in an if statement for any purpose, but they are most useful in the context of tracing along with System.Diagnostics.Trace.WriteIf:
VB
Dim aSwitch As New BooleanSwitch("ImportantSwitch", "Show errors")
System.Diagnostics.Trace.WriteIf(aSwitch.Enabled, "The Switch is enabled!")
C#
BooleanSwitch aSwitch = new BooleanSwitch("ImportantSwitch", "Show errors");
System.Diagnostics.Trace.WriteIf(aSwitch.Enabled, "The Switch is enabled!");
If ImportantSwitch is set to 1, or a non-zero value, in the config file, the call to WriteIf sends a string to trace output.
TraceSwitch offers five levels of tracing from 0 to 4, implying an increasing order: Off, Error, Warning, Info, and Verbose. You construct a TraceSwitch exactly as you create a BooleanSwitch:
VB
Dim tSwitch As New TraceSwitch("LevelSwitch", "Trace Levels")
System.Diagnostics.Trace.WriteIf(tSwitch.TraceInfo, "The Switch is 3 or more!")
C#
TraceSwitch tSwitch = new TraceSwitch("LevelSwitch", "Trace Levels");
System.Diagnostics.Trace.WriteIf(tSwitch.TraceInfo, "The Switch is 3 or more!");
A number of properties on the TraceSwitch class return true if the switch is at the same level or at a higher level than the property’s value. For example, the TraceInfo property returns true if the switch’s value is set to 3 or more.
Since the release of the .NET Framework 2.0, you have been able to use SourceSwitch, which is similar to TraceSwitch but provides a greater level of granularity. You call SourceSwitch.ShouldTrace with an EventType as the parameter:
VB
Dim sSwitch As New SourceSwitch("SourceSwitch", "Even More Levels")
System.Diagnostics.Trace.WriteIf(sSwitch.ShouldTrace(TraceEventType.Warning),
"The Switch is 3 or more!")
C#
SourceSwitch sSwitch = new SourceSwitch("SourceSwitch", " Even More Levels");
System.Diagnostics.Trace.WriteIf(sSwitch.ShouldTrace(TraceEventType.Warning),
"The Switch is 4 or more!");
It does not exactly qualify as debugging, but you will find a series of application-monitoring and health-monitoring tools in ASP.NET’s System.Web.Management namespace in ASP.NET. These tools can be as valuable as tracing information in helping you monitor, maintain, and diagnose the health of your application. The system has an event model and event engine that can update your application with runtime details. It has a number of built-in events, including application lifetime events such as start and stop and a heartbeat event. You can take these base classes and events and build on them to create events of your own. For example, you might want to create an event that tells you when a user downloads a particularly large file or when a new user is created in your personalization database. You can have your application send an e-mail to you once a day with statistics.
For instance, you can create your own event by deriving from System.Web.Management.WebBaseEvent, as shown in Listing 29-4.
LISTING 29-4: Web events
Imports System
Imports System.Web.Management
VB
Namespace Wrox
Public Class WroxEvent
Inherits WebBaseEvent
Public Const WroxEventCode As Integer = WebEventCodes.WebExtendedBase + 1
Public Sub New(ByVal message As String, ByVal eventSource As Object)
MyBase.New(message, eventSource, WroxEventCode)
End Sub
End Class
End Namespace
C#
namespace Wrox
{
using System;
using System.Web.Management;
public class WroxEvent: WebBaseEvent
{
public const int WroxEventCode = WebEventCodes.WebExtendedBase + 1;
public WroxEvent(string message, object eventSource) :
base(message, eventSource, WroxEventCode){}
}
}
Later, in a sample Page_Load, you raise this event to the management subsystem:
VB
Protected Sub Page_Load(sender As Object, e As EventArgs)
' Raise a custom event
Dim anEvent As Wrox.WroxEvent = New Wrox.WroxEvent("Someone visited here", Me)
anEvent.Raise()
End Sub
C#
protected void Page_Load(Object sender, EventArgs e)
{
// Raise a custom event
Wrox.WroxEvent anEvent = new Wrox.WroxEvent("Someone visited here!", this);
anEvent.Raise();
}
The event is caught by the management subsystem and can be dispatched to different providers based on a number of rules. This is a much more formal kind of tracing than a call to Trace.WriteLine, so you create a strongly typed event class for events specific to your application:
WEB.CONFIG
<?xml version="1.0"?>
<configuration>
<system.web>
<healthMonitoring enabled="true">
<providers>
<add name="WroxDatabaseLoggingProvider"
type="System.Web.Management.SqlWebEventProvider"
connectionStringName="QuickStartSqlServer"
maxEventDetailsLength="1073741823"
buffer="false"/>
</providers>
<rules>
<add
name="Application Lifetime Events Rule"
eventName="All Events"
provider="WroxDatabaseLoggingProvider"
profile="Critical" />
</rules>
</healthMonitoring>
</system.web>
</configuration>
Visual Studio includes two configurations by default: debug and release. The debug configuration automatically defines the debug and trace constants, enabling your application to provide context to a troubleshooter. The option to generate debugging information is turned on by default, causing a program database (or debug) file (PDB) to be generated for each assembly and your solution. They appear in the same bin folder as your assemblies. Remember, however, that the actual compilation to native code does not occur in Visual Studio, but rather at run time using Just-In-Time compilation (JIT). The JIT will automatically optimize your code for speed. Optimized code, however, is considerably harder to debug because the operations that are generated may not correspond directly to lines in your source code. For debug purposes, this option is set to false.
The PDBs are created when either the C# compiler (csc.exe) or Visual Basic compiler (vbc.exe) is invoked with the /debug:full command-line switch. As an option, if you use /debug:pdbonly, you will generate PDBs but still direct the compiler to produce release-mode code.
The debug and release configurations that come with Visual Studio are generally sufficient for your needs. However, these configurations control only the compilation options of the code-behind files. Remember that, depending on how you have chosen to design your ASP.NET application, the ASP.NET .aspx files may be compiled the first time they are hit, or the entire application may compile the first time a page is hit. You can control these compilation settings via the compilation elements within the <system.web> section of your application’s web.config file. Set <compilation debug="true"> to produce binaries as you do when using the /debug:full switches. PDBs are also produced.
The average developer is most concerned with the existence of PDB files. When these files exist in your ASP.NET application’s bin folder, the run time provides you with line numbers. Of course, line numbers greatly assist in debugging. You cannot step through source code during an interactive debugging session without these files.
When an unhandled error occurs in an ASP.NET application, the default error handler for the ASP.NET worker process catches it and tries to output some HTML that expresses what happened. However, when you are debugging components outside of ASP.NET, perhaps within the context of unit testing, the debug dialog box appears when the .NET application throws an unhandled exception.
If something has gone horribly wrong with an ASP.NET application, it is conceivable that you may find a web server with the dialog box popped up waiting for your input. This can be especially inconvenient if the machine has no keyboard or monitor hooked up. The day may come when you want to turn off the debug dialog box that appears, and you have two options to do this:
You have a number of ways to enter an interactive debugging session with ASP.NET. Visual Studio can fire up the ASP.NET worker process, load your newly compiled website, and attach the debugging to the worker process automatically. Alternatively, you can attach a debugger to a site that is already running. Visual Studio also includes a new simpler remote debugging tool for cross-machine debugging.
When you start debugging an ASP.NET application, Visual Studio takes into consideration all the Start options within your project properties. It enables you to start debugging using the currently selected page. The specific page has been selected so that the Visual Studio debugger can automatically attach the correct process, which might be the Visual Studio web server, the ASP.NET worker process, or a remote debug monitor.
Jumping into an interactive debugging session of a website that is already running, and at known state, is often more convenient than starting an application from scratch each time you debug. To begin debugging a site that is already running, from Visual Studio’s Debug menu, select Attach to Process. The dialog box has been improved from previous versions of Visual Studio and includes a Refresh button and simplifies most common debugging use cases by showing only those processes that belong to the user and that are in the currently running session.
Also included is a transport drop-down with the default transport selected. The default enables you to select processes on your computer or on a remote computer that’s running the Remote Debugging Monitor. Other options are there for smart client or unmanaged debugging.
Remote debugging has gotten simpler over the years. However, in the interest of security, you must have the appropriate credentials to perform remote debugging. You will find a Remote Debugger folder in C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE.
To begin, you must set up remote debugging on the machine that contains the application you want to debug. Rather than performing a complicated installation, you can now use the Remote Debug Monitor, and an application that can simply be run off a file share. The easiest scenario has you sharing these components directly from your Visual Studio machine and then running msvsmon.exe off the share.
Simply running the Remote Debug Monitor executable off the file share can make remote ASP.NET debugging of an already-deployed application much simpler, although you still need to attach to the ASP.NET worker process manually because automatic attaching is not supported. Do note that two versions of the debugger now exist, one for x86 processes and one for x64 processes, so make sure you are using the right debugger for your process.
You are allowed to debug a process that is running under your account and password without any special permissions. If you need to debug a process running under another account name, such as an ASP.NET worker process running as a user who is not you, you must be an administrator on the machine running the process.
The most important thing to remember when debugging remotely is this: You need to get the user account that is running as Visual Studio to map somehow to a legitimate user account on the machine running the Remote Debug Monitor (msvsmon.exe) machine and vice versa. The easiest way to do this is to create a local user account on both computers with the same username and password. To run msvsmon as a user other than Visual Studio, you must create two user accounts on each computer.
If one of the machines is located on a domain, be aware that domain accounts can be mapped to a local account. You create a local user account on both computers. However, if you pick the same username and password as your domain account, Visual Studio can be run as a domain account.
Visual Studio’s debugging experience offers a number of tools — some obvious, some more subtle — to assist you in every step of the debug session.
Visual Studio offers DataTips, allowing complex types to be explored using a modeless tree-style view that acts like a tooltip and provides much more information. After you traverse the tree to the node that you are interested in, you can view that simple type using a visualizer by clicking the small magnifying glass icon, as shown in Figure 29-10.
Floating the DataTips is also possible by clicking once again on the pin of an already pinned DataTip. Doing so enables you to move the DataTip anywhere you want on the screen. Now if you scroll your code, the DataTip remains in place on the screen regardless of what you are doing in Visual Studio. The nice thing with floating your DataTips is that you can move them outside of the code window, and you can even move them to a second monitor if you want.
In a pinned DataTips toolbar, notice a button that enables you to put a comment in the DataTip that stays with it as you move it around. Figure 29-11 shows a DataTip with a comment.
As you saw earlier, you can view a simple type using any number of data visualizers. For example, if a simple variable such as a string contains a fragment of XML, you might want to visualize that data in a style that is more appropriate for the data’s native format, as shown in Figure 29-12.
The visualizers are straightforward to write and, although Visual Studio ships with default visualizers for text, HTML, XML, and DataSets, look on the Internet for additional visualizers that might include support for images, collection classes, and more. The result is a rich, unparalleled debugging experience.
During an interactive debugging session, Visual Studio assists you with informative error notifications. These notifications not only report on events such as unhandled exceptions, but also offer context-sensitive troubleshooting tips and next steps for dealing with the situation. Figure 29-13 shows an unhandled NullReferenceException along with the good advice that you might try using the “new” keyword to create an object instance before using it. Oops!
Though Visual Basic offers the Edit and Continue feature, which gives you the capability to change code during a debugging session without restarting the session (in break mode, you can modify code fix bugs and move on) for both C# and VB, unfortunately this feature is not available to ASP.NET developers.
In ASP.NET, your assembly is compiled not by Visual Studio, but by the ASP.NET run time using the same technique it does during a normal web page request by a browser. To cooperate with the debugger and support Edit and Continue within ASP.NET, a number of fantastically complex modifications to the ASP.NET run time would have been required by the development team. Rather than including support for this feature, ASP.NET developers can use page recycling.
This means that code changes are made during a debugging session, and then the whole page is refreshed via F5, automatically recompiled, and re-executed. Basically, ASP.NET includes support for Edit and Refresh, but not for Edit and Continue.
Another concept in the .NET Framework is called Just My Code debugging. Any method in code can be explicitly marked with the attribute [DebuggerNonUserCode]. Using this explicit technique and a number of other heuristic methods internal to the CLR, the debugger silently skips over code that is not important to the code at hand. You can find the preference Enable Just My Code in Tools ⇒ Options ⇒ Debugging.
The [DebuggerHidden] attribute is still available in .NET and hides methods from the debugger, regardless of the user’s Just My Code preference. The .NET Framework 1.1 attribute [DebuggerStepThrough] tells the debugger to step through, rather than into, any method to which it is applied; the [DebuggerNonUserCode] attribute is a much more pervasive and complete implementation that works at run time on delegates, virtual functions, and any arbitrarily complex code.
Be aware that these attributes and this user option exist to help you debug code effectively and not be fooled by any confusing call stacks. Although these can be very useful, be sure not to use them on your components until you are sure you will not accidentally hide the very error you are trying to debug. Typically, these attributes are used for components such as proxies or thin shim layers.
Breakpoints by themselves are useful for stopping execution either conditionally or unconditionally. Standard breakpoints break always. Conditional breakpoints cause you to enter an interactive debugging session based on a condition. Tracing is useful to output the value of a variable or assertion to the debugger or to another location. If you combine all these features, what do you get? Tracepoints, a powerful Visual Studio feature. Tracepoints can save you from hitting breakpoints dozens of times just to catch an edge case variable value. They can save you from covering your code with breakpoints to catch a strange case.
To insert a tracepoint, right-click in the code editor and select Breakpoint ⇒ Insert Tracepoint. You get the dialog box shown in Figure 29-14. The icon that indicates a breakpoint is a red circle, and the icon for a tracepoint is a red diamond. Arbitrary strings can be created from the dialog box using pseudo-variables in the form of keywords such as $CALLSTACK or $FUNCTION, as well as the values of variables in scope placed in curly braces. In Figure 29-14, the value of i.Name (placed in curly braces) is shown in the complete string with the Debug output of Visual Studio.
Right-click a breakpoint in your code to access a number of options that enable you to manage some of the identifiers and behaviors of your breakpoints.
One option is to set a condition for your breakpoint. To get to the Breakpoint Condition dialog box, right-click the breakpoint and select Condition from the provided menu. Here you are presented with a textbox that enables you to create conditions that trigger the breakpoint.
Just as you could with DataTips, Visual Studio 2012 enables you to export your breakpoints. To export all the breakpoints, right-click any breakpoint and select Export from the provided menu. This export enables you to save all the breakpoints in an XML file that then can be loaded by someone else. It is a convenient way to distribute these items.
One of the more exciting improvements of Visual Studio 2012 is IntelliTrace. IntelliTrace provides you with a historical debugger that enables you to look back in the history of your application running and allows you to jump at previous points in time to see what was occurring.
This feature could only be used in development environments with Visual Studio 2010, but with Visual Studio 2012 it can be used in production environments as well. IntelliTrace supports a lot more features in Visual Studio 2012. You have access to rich debugging information that can show you past events along with call information of your application. You can use IntelliTrace along with Test Manager support in Visual Studio, which collects Diagnostics Trace Data that can be used to find bugs that are difficult to reproduce.
When stopping at a breakpoint within your application, you will now be able to see this history in the IntelliTrace dialog box directly, as illustrated in Figure 29-15.
From here, you can jump to any point in the past and view the Locals dialog box values and the Call Stack dialog box values for that moment in time. The IntelliTrace capability is configurable as well. You can get to these settings by either clicking Open IntelliTrace Settings from the IntelliTrace toolbar or by selecting Tools ⇒ Options and selecting IntelliTrace from the items in the left side of the dialog box.
From the IntelliTrace Events tab, you can select the events that you want to work with. You can choose from a large series of technologies and situations by making the appropriate selections.
Finally, the Modules tab enables you to specify particular modules that IntelliTrace should work with.
Visual Studio 2012 includes a dialog box that enables you to see what is going on in each of the threads your application is using. You do this through the Threads dialog box shown in Figure 29-16.
You can double-click a thread and go to where that thread is in the code when debugging. You can also get a visual family tree view of the threads at work by opening up the Parallel Stacks dialog box.
Excellent client-side JavaScript debugging is available in Visual Studio 2012. If you run an ASP.NET application in a debugging session in Internet Explorer, you will need to enable script debugging.
After you have turned on script debugging, try a simple ASP.NET page with some JavaScript that changes the text in a textbox to UPPERCASE when the button is clicked (Listing 29-5).
LISTING 29-5: Simple JavaScript debugging test
ASPX
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<script type="text/javascript">
function MakeItUpper()
{
newText = document.getElementById("TextBox1").value.toUpperCase();
document.getElementById("TextBox1").value = newText;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="button" id="Button1" value="Upper"
onclick="javascript:MakeItUpper()" />
<input type="text" id="TextBox1" runat="server"/>
</div>
</form>
</body>
</html>
Put a breakpoint on one of the lines of client-side JavaScript. Note that this is code that runs in the browser, not on the web server. Start a debugging session with the page from Listing 29-5. Visual Studio will break at that point, as shown in Figure 29-17.
The JavaScript debugger in Visual Studio supports variable tooltips, visualizers, call stacks, locals, watches, and all the features you’re used to when debugging .NET-based languages.
Database projects are file-based projects that enable you to manage and execute database queries. You can add your existing SQL scripts to the project or create new ones and edit them within Visual Studio. Database projects and SQL debugging are not available in the Express or Standard versions of Visual Studio. They are available only in the Professional or Team Edition Visual Studio SKUs/versions.
When debugging database applications, you cannot use Step Into (F11) to step between code in the application tier into the code in SQL Server (be it T-SQL or CLR SQL). However, you can set a breakpoint in the stored procedure code and use Continue (F5) to execute code to that set breakpoint.
When debugging SQL on SQL Server 2012, be aware of any software or hardware firewalls you may be running. Sometimes your software firewall will warn you what you are trying to do. Be sure to select “unblock” in any warning dialog boxes to ensure that SQL Server 2012 and Visual Studio can communicate.
If you are using a SQL account to connect to SQL Server, make sure the Windows User Account you run Visual Studio under is also an administrator on the SQL Server machine. You can add accounts to SQL Server’s sysadmin privilege using the SQL command sp_addsrvrolemember 'Domain\Name', 'sysadmin'. Of course, never do this in production; and better yet, do your debugging on a machine with everything installed locally.
If you are using the NT Authentication model on SQL Server 2012, make sure that account has permissions to run the sp_enable_sql_debug stored procedure. You can give account access to this stored procedure by using the SQL commands CREATE USER UserName FOR LOGIN 'Domain\Name' followed by GRANT EXECUTE ON sp_enable_sql_debug TO UserName. This creates a SQL user who is associated directly with a specific Windows User and then explicitly grants permissions to debug T-SQL to that user. On SQL Server 2000, the user must have access to the extended stored procedure sp_sdidebug.
When an exception occurs in your ASP.NET application code, you can handle it in a number of ways, but the best approach is a multi-pronged one:
The phrase unhandled exception may be alarming, but remember that you don’t do anyone any good catching an exception that you can’t recover from. Unhandled exceptions are okay if they are just that — exceptional. For these situations, rely on global exception handlers for logging and friendly error pages that you can present to the user.
To handle exceptions at a page level, override the OnError method that System.Web.UI.Page inherits from the TemplateControl class (see Listing 29-6). Calling Server.GetLastError gives you access to the exception that just occurred. Be aware that a chain of exceptions may have occurred, and you can use the ExceptionGetBaseException method to return the root exception.
LISTING 29-6: Page-level error handling
VB
Protected Overrides Sub OnError(ByVal e As System.EventArgs)
Dim AnError As System.Exception = Server.GetLastError()
If (TypeOf AnError.GetBaseException() Is SomeSpecificException) Then
Response.Write("Something bad happened!")
Response.StatusCode = 200
Server.ClearError()
Response.End()
End If
End Sub
C#
protected override void OnError(EventArgs e)
{
System.Exception anError = Server.GetLastError();
if (anError.GetBaseException() is SomeSpecificException)
{
Response.Write("Something bad happened!");
Response.StatusCode = 200;
Server.ClearError();
Response.End();
}
}
The technique of catching exceptions in a centralized location can be applied to error handling at the application level in Global.asax, as shown in Listing 29-7. If an exception is not caught on the page, the web.config file is checked for an alternate error page; if there is not one, the exception bubbles up to the application and your user sees a complete call stack.
LISTING 29-7: Application-level error handling
VB
Protected Sub Application_Error(sender as Object, ByVal e As System.EventArgs)
Dim bigError As System.Exception = Server.GetLastError()
'Example checking for HttpRequestValidationException
If (TypeOf bigError.GetBaseException() Is HttpRequestValidationException) Then
System.Diagnostics.Trace.WriteLine(bigError.ToString)
Server.ClearError()
End If
End Sub
C#
protected void Application_Error(Object sender, EventArgs e)
{
System.Exception bigError = Server.GetLastError();
//Example checking for HttpRequestValidationException
if(bigError.GetBaseException() is HttpRequestValidationException )
{
System.Diagnostics.Trace.WriteLine(bigError.ToString());
Server.ClearError();
}
}
Unhandled application errors turn into HTTP Status Code 500 and display errors in the browser. These errors, including the complete call stack and other technical details, may be useful during development, but are hardly useful at production time. Most often, you want to create an error handler (as shown previously) to log your error and to give the user a friendlier page to view.
Every HttpRequest results in an HttpResponse, and every HttpResponse includes a status code. Table 29-2 describes 11 particularly interesting HTTP status codes.
STATUS CODE | EXPLANATION |
200 OK | Everything went well. |
301 Moved Permanently | Reminds the caller to use a new, permanent URL rather than the one he used to get here. |
302 Found | Returned during a Response.Redirect. This is the way to say “No, no, look over here right now.” |
304 Not Modified | Returned as the result of a conditional GET when a requested document has not been modified. It is the basis of all browser-based caching. An HTTP message-body must not be returned when using a 304. |
307 Temporary Redirect | Redirects calls to ASMX Web Services to alternate URLs. Rarely used with ASP.NET. |
400 Bad Request | Request was malformed. |
401 Unauthorized | Request requires authentication from the user. |
403 Forbidden | Authentication has failed, indicating that the server understood the request but cannot fulfill it. |
404 Not Found | The server has not found an appropriate file or handler to handle this request. The implication is that this may be a temporary state. This happens in ASP.NET not only because a file cannot be found, but also because it may be inappropriately mapped to an IHttpHandler that was not available to service the request. |
410 Gone | The equivalent of a permanent 404 indicating to the client that it should delete any references to this link if possible. 404s usually indicate that the server does not know whether the condition is permanent. |
500 Internal Server Error | The official text for this error is “The server encountered an unexpected condition which prevented it from fulfilling the request,” but this error can occur when any unhandled exception bubbles all the way up to the user from ASP.NET. |
Any status code greater than or equal to 400 is considered an error and, unless you configure otherwise, the user will likely see an unfriendly message in his or her browser. If you have not already handled these errors inside of the ASP.NET run time by checking their exception types, or if the error occurred outside of ASP.NET and you want to show the user a friendly message, you can assign pages to any status code within the web.config file, as the following example shows:
<customErrors mode ="On" >
<error statusCode ="500" redirect ="FriendlyMassiveError.aspx" />
</customErrors>
After making a change to the customer errors section of your web.config file, make sure a page is available to be shown to the user. A classic mistake in error redirection is redirecting the user to a page that will cause an error, thereby getting him stuck in a loop. Use a great deal of care if you have complicated headers or footers in your application that might cause an error if they appear on an error page. Avoid hitting the database or performing any other backend operation that requires either user authorization or that the user’s session be in any specific state. In other words, make sure that the error page is a reliable standalone.
Page Inspector is a new tool option in Visual Studio 2012 that brings the diagnostics tools experience found in browsers to Visual Studio. This means that by using Page Inspector you can inspect the elements and see which file and line of source code generated the markup. Page Inspector also lets you inspect the DOM elements as well as CSS elements so that you can modify these properties and see the changes in real time.
Figure 29-18 shows what an application looks like when loaded in Page Inspector.
If you want to load an application in Page Inspector then you can right-click your project and select the “View in Page Inspector” option. Figure 29-19 shows this option in Visual Studio 2012. When you click this option, Visual Studio compiles your project and loads the application in Page Inspector.
The primary function of Page Inspector is Source Mapping. When you inspect a certain element on the page, Page Inspector will open the appropriate file in the preview mode in Visual Studio and highlight the line of source code which generated that element. For example, Figure 29-20 shows the Default.aspx in preview mode and highlights the Page Title property when you inspect the title on the page in Page Inspector.
Page Inspector also supports JavaScript mapping. This means that Page Inspector can be used to figure out which file or line of source code in the JavaScript files is being used on the page that you are browsing in your application. For example, Figure 29-21 shows an SPA application that uses Knockout. When you inspect an element, then the Call Stack tab of Page Inspector shows you the JavaScript call stack that resulted in this element being created. You can click any one of the call stack functions, and Page Inspector will take you to the exact line of JavaScript code. This is very useful when you are building applications that use a lot of JavaScript and the JavaScript code is modifying the DOM elements.
As you learned earlier, Page Inspector can be used to interact with the DOM and CSS tools to modify the CSS properties and see the changes in real time. For example, to modify the CSS properties, you can select the “Styles” tab in Page Inspector and see all the CSS styles that are used on the web page. Suppose you wanted to change the background color of the web page; then you can select the background-color property, change the color, save the CSS file, and refresh the page in Page Inspector to see the changes.
This chapter examined the debugging tools available to you for creating robust ASP.NET applications. A successful debugging experience includes not only interactive debugging with features such as DataTips, data visualizers, and error notifications, but also powerful options around configurable tracing and logging of information.
Remote debugging is easier than ever with ASP.NET, and the capability to write and debug ASP.NET pages without installing IIS removes yet another layer of complexity from the development process.
Visual Studio and its extensible debugging mechanisms continue to be expanded by intrepid bloggers and enthusiasts, which makes debugging easier every day.