Magazine > Issues > 2006 > June >  Cutting Edge: A Provider-Based Service for ASP....
Cutting Edge
A Provider-Based Service for ASP.NET Tracing
Dino Esposito

Code download available at: CuttingEdge2006_06.exe (190 KB)
Browse the Code Online
When it comes to catching programming errors, the debugger is a developer's best friend. ASP.NET tracing, however, is a nice complement to the debugger and shouldn't be overlooked. It enables your ASP.NET code to emit messages during processing, offering valuable information that can prove quite useful for detecting problems.
The two techniques are a bit different. Using a debugger is an inherently interactive technique that relies on being able to pause execution and examine current state. Tracing is less intrusive, simply tracking information being emitted by the code (similar to the classic "printf" style of debugging). At the end of the execution, you take the output produced by the tracing activity and analyze it.
The ASP.NET tracing subsystem exposes an API through which any control, page, or component can add its own trace output. You can output any information available at run time that makes sense to you.
In ASP.NET, tracing is tightly integrated with the page and the runtime pipeline. You can enable tracing by turning on an attribute in individual pages. To enable tracing for all pages in an application, you change a value in the configuration file. (More on this in a moment.) This means it's possible, for example, to enable tracing for an application after it has been deployed.
In this month's column, I'll briefly review ASP.NET tracing basics and discuss what's new in ASP.NET 2.0. Finally, I'll explore ways to customize the appearance of traced text in ASP.NET pages.

Tracing in ASP.NET
Tracing is built right into the infrastructure of ASP.NET as a subsystem that provides applications with diagnostic information about individual requests. Through tracing, you can analyze data communicated between a browser and a Web server, including request details, timing information, server variables, cookies, headers, session state, and so on.
When tracing for a page is enabled, ASP.NET can display this information in one of two ways: it can append diagnostic information to the page's output, and it can render the information through a built-in trace viewer application. To see trace information appended to the bottom of a particular page, you simply set the Trace attribute of the @Page directive to true, like so:
<%@Page trace="true" ...  %>
Note that in this case the trace information is sent as part of the page and as such it displays through the browser. This is not an issue when you're creating and testing the page. However, it becomes a nasty bug if you forget to reset a Trace attribute in one of your pages before deployment, thereby allowing the world to see this (potentially sensitive) information.
For the most part, the content and layout of trace information is fixed, but page and control authors can customize the output to some extent. In ASP.NET 2.0, the trace output is divided into 12 distinct sections, as outlined in Figure 1.

Section Name What It Displays
Request Details Information about the current request, such as status code, verb, session ID, and timestamp
Trace Information System- and user-defined messages sorted by time or category
Control Tree The hierarchy of the controls in the page, including each corresponding view state size
Session State All the items stored in Session
Application State All the items stored in the Application intrinsic object
Request Cookies Collection Name and content of all cookies attached to the request (ASP.NET 2.0 only)
Response Cookies Collection Name and content of all cookies attached to the response (ASP.NET 2.0 only)
Headers Collection Name and content of all request headers
Response Headers Collection Name and content of all headers attached to the response (ASP.NET 2.0 only)
Form Collection Name and content of all input fields packed in the HTTP response
Querystring Collection Name and content of all query string parameters
Server Variables Name and content of all server variables available
In ASP.NET 1.x there was only one Cookies Collection object and it included both request and response cookies. There was also only one Headers Collection, and it included both request and response headers.
You can use the methods of the TraceContext class to emit specific messages and information in the Trace Information section. You use the following code:
' In a Page-derived type
Trace.Write("Your message")

' Anywhere in an ASP.NET application
HttpContext.Current.Trace.Write("Your message")
An instance of the TraceContext class is exposed to developers through the Trace property of the HttpContext class and is mirrored by the Trace property on the Page class.
The message is composed in the trace output based on the time it is executed. For example, if you emit a message in the Page_Load event of a page, the message will appear as shown in Figure 2. The TraceContext class also features a Warn method. Warn differs from Write in that it renders its text in red. Both the Write and Warn methods have a few overloads:
' Write overloads
Public Sub Write(String)
Public Sub Write(String, String)
Public Sub Write(String, String, Exception)

' Match Warn overloads
Public Sub Warn(String)
Public Sub Warn(String, String)
Public Sub Warn(String, String, Exception)
The first overload writes the specified text in the Message column (see Figure 2). The second overload accepts the name of the category and the message. The category name, which can be used to sort trace information, can be any string that makes sense to the application and the message. The third overload adds an extra Exception object in case the message is tracing an error. In this case, the text in the Message column also includes the exception's message. No HTML formatting is accepted in the trace output as the text is escaped before rendering to the browser.
Figure 2 Trace Output in an ASP.NET Page 
The TraceContext class also features a couple of properties: IsEnabled and TraceMode. IsEnabled is a Boolean property that gets and sets whether tracing is on. TraceMode property gets and sets the order in which the traced rows will be displayed in the page. The property is of type TraceMode—an enumeration that includes values such as SortByCategory and SortByTime. You can enable tracing programmatically by setting IsEnabled. In ASP.NET 2.0, you can also use the new property, TraceEnabled, on the Page class. This property just wraps Trace.IsEnabled.

The <trace> Element
As mentioned, most of the tracing attributes can be controlled from within the configuration file. There is an option that allows you to capture trace information without emitting text in the page served to the browser. This feature is extremely useful for tracking data inconsistencies, monitoring the flow, and asserting conditions on applications already deployed in a production environment. The configuration section of interest is <trace>, which looks like this in ASP.NET 2.0:
<trace 
   enabled="true|false"
   localOnly="true|false"
   mostRecent="true|false"
   pageOutput="true|false"
   requestLimit="integer" 
   traceMode="SortByTime|SortByCategory"
   writeToDiagnosticsTrace="true|false" />
The various attributes are detailed in Figure 3.

Section Name What It Indicates
enabled Whether tracing is enabled through the trace.axd viewer. This attribute alone is not sufficient to append trace information to all pages. The default is false.
localOnly Whether the trace.axd viewer should be available only on the local Web server. The default is true.
mostRecent The tracing system only maintains tracing information on a specific number of requests, as determined by requestLimit. When mostRecent is true, information from newer requests overwrites that from older requests when the limit has been reached. The default is false, meaning that trace data is recorded only until the limit is reached, causing subsequent traces to be lost (ASP.NET 2.0 only).
pageOutput Whether trace output is rendered inside each page of the application. If false, trace output is only accessible through the trace.axd viewer. The default is false.
requestLimit The number of trace requests to store on the server. The default is 10; the maximum is 10,000.
traceMode The order in which trace information is displayed: sorted by category or by time. The default is SortByTime.
writeToDiagnosticsTrace Whether trace output should be forwarded to any .NET trace listeners that are registered. The default is false (ASP.NET 2.0 only).
ASP.NET also supports application-level tracing through a built-in trace viewer tool—trace.axd. Note that the enabled attribute of the <trace> section activates this tool, but doesn't activate trace text in each page. This was done intentionally to let you to turn on tracing in production without affecting the output of pages served to users.
You might want to leave tracing statements in your published application but turn off Trace attributes in individual pages and disable tracing in the web.config file. The application won't incur noticeable overhead in normal conditions, and this leaves you the option to turn on tracing at any time by tweaking the web.config file as follows:
<trace enabled="true" pageOutput="false" />
Once tracing has been enabled for the application, each page request routes all the page-specific trace information to the viewer. Trace.axd (shown in Figure 4) is a system-defined HTTP handler that retrievs the internal cache of trace information and displays it in a Web interface. You can access the trace viewer by requesting trace.axd from the root application directory. You can also directly access the trace information of a particular request by adding the id=XXX parameter to the query string. XXX indicates the 0-based index of the requests where 0 represents the oldest.
Figure 4 Trace Viewer 
In ASP.NET 1.x, only 10 requests are traced by default (the default value of requestLimit in <trace>). In ASP.NET 2.0, you can keep the 10 most recent, overwriting older traces (or whatever value is specified by requestLimit).
All the trace information is cached inside the TraceContext class using a DataSet object. An instance of the TraceContext class is created upon the first initialization of the HttpRuntime object for the application's AppDomain. As a result, when trace.axd is enabled, the more trace information you maintain, the more you pay in terms of memory consumption.
A new feature of ASP.NET 2.0 lets you subscribe to the TraceContext.TraceFinished event, which is raised when all request information has been captured. An event handler for TraceFinished is provided with a TraceFinishedEventArgs, which exposes a collection of TraceRecord objects. Each of these stores the information from an individual ASP.NET trace message and any associated data, allowing you to capture all of this information and do with it as you please, showing it on the page, storing it to your own database, and so on.

Connecting to Diagnostic Listeners
Tracing in ASP.NET is somewhat orthogonal to tracing in the .NET Framework. The Systems.Diagnostics namespace defines two classes, Trace and Debug, whose methods are used to trace the code. The Trace and Debug classes are essentially identical and work on top of more specialized modules known as listeners. A listener collects and stores messages in the Windows event log, a text file, or some other similar file. Each application can have its own set of listeners; all registered listeners receive all emitted messages. The tracing subsystem of ASP.NET 2.0 has been enhanced to include support for forwarding ASP.NET tracing information to any registered .NET trace listeners. You enable this feature by turning on the writeToDiagnosticsTrace attribute in the <trace> section of the web.config file, like so:
<trace enabled="true" pageOutput="false" 
       writeToDiagnosticsTrace="true" />
Setting the writeToDiagnosticsTrace attribute is not enough, though. You also need to register one or more listeners for the Web application. Use the <trace> section under the <system.diagnostics> section to do that, as shown here:
<system.diagnostics>
  <trace autoflush="true">
    <listeners>
      <add name="myListener" 
           type="System.Diagnostics.TextWriterTraceListener" 
           initializeData="c:\myListener.log" />
    </listeners>
  </trace>
</system.diagnostics>
There are two important things to note here. First, only text written through either Write or Warn is forwarded to the listeners. That is, the listener won't store a copy of the standard ASP.NET trace information; the information tracked is limited to the contents of the Trace Information section. The second thing is that you can write custom listeners to send trace information to the medium of your choice—for example, a SQL Server database. All you have to do is inherit a class from System.Diagnostics.TraceListener and override a few methods. Next, you register the new class with the web.config file of the Web application you plan to trace. (For more information on writing custom TraceListener types, see msdn.microsoft.com/msdnmag/issues/06/04/CLRInsideOut.) One interesting thing to note is that System.Web.dll now includes a TraceListener-derived type, WebPageTraceListener, whose job is to serve as the counterpart to writeToDiagnosticsTrace. When an instance of WebPageTraceListener is added to the trace listeners collection, messages written to the .NET diagnostics trace will also be written to the ASP.NET trace.
Finally, you should be aware that in ASP.NET 2.0 a new <deployment> section has been added to the Web configuration section related to tracing.
<deployment retail="true|false" />
When the retail attribute is set to true, the ASP.NET runtime disables trace output, custom errors, and debug capabilities. The <deployment> section can only be set in the machine.config file.

Taking Control of the Trace
The standard trace text that ASP.NET emits in the page contains twelve sections (as in Figure 1) whose overall size is about 20KB of additional data. The size and format of this data is typically not an issue, since you'll be tracing pages only during the testing and development stages. However, a bit of control over the format of the traced text can be helpful, allowing you to increase your ability to quickly read that data.
ASP.NET trace data, however, is hard to catch. The data is collected during the request processing and cached internally by the TraceContext class. When the request processing is completed, the trace information is output. Tracing is controlled by the ProcessRequest method of the Page class—the central method of any ASP.NET page request. Basically, the trace information is flushed only when ProcessRequest is finished with the page markup. How can you capture the trace text? How can you post-process trace information to append additional data, remove unnecessary sections, or just format it differently?
In my September 2004 column, I provided a page-specific solution that was difficult to extend to all pages in the application. The idea is quite simple. You wrap the response stream with a custom stream and give yourself a chance to post-process everything being sent through the page's output stream—the page body, but also the traced text.
To obtain this, you need to set the Filter property of the HttpResponse object to a custom stream object. The Filter property can be used to set a wrapping filter object to modify the HTTP body before transmission. In the September 2004 column I just did this from within a sample page, and it worked fine. But what if you want to enable this feature for all pages in the application? What if you want to automatically filter the trace text in all the pages where tracing is enabled? And what if you want to do this without touching the source code of the page? Read on.

The MyTracer HTTP Module
The idea is to install an HTTP module to overwrite the Filter property of all response objects belonging to pages where tracing is enabled. An HTTP module is a class that implements the IHttpModule interface and is registered under the <httpModules> section of the application's web.config file. Figure 5 shows the source code of the MyTracer module I'm using to lay the foundations of a custom ASP.NET trace service.
Namespace MyTracer
    Public Class TracerModule : Implements IHttpModule
        Public ReadOnly Property ModuleName() As String
            Get
                Return "MyTracerModule"
            End Get
        End Property

        Sub Init(ByVal app As HttpApplication) Implements _
            IHttpModule.Init
            AddHandler app.PostRequestHandlerExecute, _
                AddressOf OnPostRequestHandlerExecute
        End Sub

        Public Sub Dispose() Implements IHttpModule.Dispose
        End Sub

        Private Sub OnPostRequestHandlerExecute( _
                ByVal source As Object, ByVal e As EventArgs)
            Dim application As HttpApplication = _
                DirectCast(source, HttpApplication)
            Dim context As HttpContext = application.Context
            If context.Trace.IsEnabled Then
                Dim response As HttpResponse = context.Response
                If Not response Is Nothing Then
                    response.Filter = New TraceStream(response.Filter)
                End If
            End If
        End Sub
    End Class
End Namespace
The module listens to an application-level event named PostRequestHandlerExecute. That event occurs immediately after the request processing has terminated. More precisely, the event is notified immediately after the ProcessRequest call to the HTTP handler in charge of the page request has returned. When this happens, the trace information has been generated and flushed but the page response is still held on the server and not sent to the browser. The module registers the event handler in its Init method.
As you can see in Figure 5, the event handler checks the value of the IsEnabled property on the Trace object and, if tracing is enabled for the request, sets the Filter property of the response to a custom stream object. The net effect of this operation is that all the response text passes through the custom stream object and can be read and modified at will. Why can't you just use the original Filter stream object, reading from it and writing back to it, to do the required manipulation? For performance reasons, the stream object returned by the Filter property is write-only and throws an exception as soon as you try to call any of the read methods.
Figure 6 shows the source code of the TraceStream sample class. It is a memory stream class that overrides the Write method to post-process the byte array being sent to the browser. The byte array is transformed into a string, parsed as necessary, and, when done, written back to the browser. The heart of the new, smarter tracing service is the TraceOutputService class.
Public Class TraceStream : Inherits MemoryStream
    Private _outputStream As Stream

    Public Sub New(ByVal outputStream As Stream)
        _outputStream = outputStream
    End Sub

Public Overrides Sub Write( _
            ByVal buffer As Byte(), ByVal offset As Integer, _
            ByVal count As Integer)
        
        Dim enc As New ASCIIEncoding
        Dim pageResponse As String = enc.GetString(buffer, offset, count)

        Dim newPageResponse As String = _ 
            TraceOutputService.Parse(pageResponse)

        Dim data() As Byte = enc.GetBytes(newPageResponse)
        _outputStream.Write(data, 0, data.Length)
    End Sub
End Class

Before I delve deeply into the TraceOutputService class, let's briefly recap. Take a look at the following script:
<httpModules>
  <add name="MyTracerModule" 
       type="MsdnMag.CuttingEdge.MyTracer.TracerModule, MyTracer" />
</httpModules>
By simply adding this to the web.config file, you apply an output filter to the page output, enabling yourself to edit virtually everything—including the trace information.

Tracing as a Provider-Based Service
To process the trace information, you might want to separate the regular page output from the standard trace information and then process the latter at will. Here's where the aforementioned TraceOutputService class comes into play.
The idea is to turn this feature into a provider-based service. You call a static method on the service class. The method, in turn, locates the default provider for the tracing service and invokes it. By registering distinct providers, you can process the trace information in any number of different ways—excluding some sections from view, collapsing other sections, adding new sections, displaying everything through a popup window, saving sections to a database, and so on.
There are various levels of complexity and flexibility to choose from when implementing such a service. The simplest option requires you to read a line from web.config or a custom XML file and call Activator.CreateInstance to instantiate the specified object. You then use a contracted interface on the object to post-process the trace information. Another option is to supply an explicit implementation of the factory pattern—more or less what happens in ADO.NET 2.0 with the DbProviderFactories class.
But of all the possibilities, the most ASP.NET-like approach is to devise the feature as a service and apply the provider model. The whole operation can be separated into three main steps: define a web.config section to collect configuration data, define a base provider class to implement the service interface, and design a service class to invoke the selected provider.
More information on how to design and author a custom provider-based service can be found at msdn.microsoft.com/library/en-us/dnaspp/html/ASPNETProvMod_Prt8.asp.

Creating a Section in Web.config
In ASP.NET 2.0, you write code to handle custom sections in the web.config file using a new and different approach than you used in ASP.NET 1.x. The idea is that you derive a class from ConfigurationSection, which is located in the System.Configuration namespace. From its parent, the new class will inherit methods and logic to parse the content of the section(s) it is bound to. Public properties on this class decorated with the ConfigurationProperty attribute are the only valid attributes in the section. To build a section for a list of providers, you can rely on some system-provided classes such as ProviderSettingsCollection (see Figure 7).
Public Class TraceOutputServiceSection : Inherits ConfigurationSection
    <ConfigurationProperty("providers")> _
    Public ReadOnly Property Providers() As ProviderSettingsCollection
        Get
            Return DirectCast(MyBase.Item("providers"), _   
                ProviderSettingsCollection)
        End Get
    End Property

    <ConfigurationProperty("defaultProvider", _
     DefaultValue:="StandardTraceOutputProvider")> _
    Public Property DefaultProvider() As String
        Get
            Return DirectCast(MyBase.Item("defaultProvider"), String)
        End Get
        Set(ByVal value As String)
            MyBase.Item("defaultProvider") = value
        End Set
    End Property
End Class

The TraceOutputServiceSection class serves a block of XML that has a subelement named <providers> and a defaultProvider attribute that has a default value of StandardTraceOutputProvider. Needless to say, a custom section—TraceOutputService in this case—must be explicitly registered with the web.config file to prevent schema complaints:
<configSections>
  <sectionGroup name="system.web">
    <section name="traceOutputService"
             type="MsdnMag.TraceOutputServiceSection, MyTracer"
             allowDefinition="MachineToApplication" />
  </sectionGroup>
</configSections>
At this point, the following configuration script is perfectly legal and correctly handled by the system:
<traceOutputService defaultProvider="RichTraceOutputProvider">
   <providers>
      <add name="StandardTraceOutputProvider"
           type="MsdnMag.StandardTraceOutputProvider, MyTracer" />
      <add name="RichTraceOutputProvider"
           type="MsdnMag.RichTraceOutputProvider, MyTracer"
           sections="All" 
           collapseAll="true" />
   </providers>
</traceOutputService>
Each item in the <providers> subsection is represented with a ProviderSettings object. This object has a name/value collection property called Parameters, which lets you freely define any set of attributes to characterize your provider's type. For more information on configuration in the .NET Framework 2.0, see Bryan Porter's article on the subject in this issue of MSDN®Magazine.

The Trace Provider Interface
A trace provider is a component that exposes a fixed interface to its caller so that the caller can polymorphically invoke any registered provider without knowing any of its implementation specifics. The provider's public contract is defined through a base class that inherits from ProviderBase:
Public MustInherit Class TraceOutputProvider : Inherits ProviderBase
    Public MustOverride Property ApplicationName As String  
    Public MustOverride Function PostProcess(  _
       ByVal tracedText As String) As String  
End Class
Note that you make this base class inherit from ProviderBase if you want to stick to the pure ASP.NET provider model. This pattern can be applied regardless of predefined ASP.NET facilities.

Devising the Service Class
Each provider-based service features an entrypoint class with a number of static methods mapped more or less directly to the members of the selected provider. The TraceOutputService class has only one purpose—parsing the page trace and applying some custom logic. For this reason, I defined a Parse static method:
Public Shared Function Parse(ByVal pageResponse As String) As String
The Parse method receives the full page content (body and trace). It first separates body from trace, and then loads the selected provider and invokes the PostProcess method on it. The return value of PostProcess is combined with the page body, returned to the stream, and then served to the user.
The TraceOutputService class features a Provider property pointing to a running instance of the selected provider and a Providers collection property that gathers all registered providers for the feature. The internal members behind the properties are static members. The class loads all providers only once for the whole lifetime of the application. Subsequently, all concurrently running requests share the same instance of the providers. That's why you must properly lock any write access to a provider's instance. Figure 8 lists the full source code of the TraceOutputService class.
Friend Class PageOutputInfo
    Public PageContent As String
    Public PageTrace As String
End Class

Public Class TraceOutputService
    Private Shared _provider As TraceOutputProvider = Nothing
    Private Shared _providers As TraceOutputProviderCollection = Nothing
    Private Shared _internalLock As New Object

    Public ReadOnly Property Provider() As TraceOutputProvider
        Get
            Return _provider
        End Get
    End Property

    Public ReadOnly Property Providers() As TraceOutputProviderCollection
        Get
            Return _providers
        End Get
    End Property

    Public Shared Function Parse(ByVal pageResponse As String) As String
        Dim info As PageOutputInfo = SplitPageResponse(pageResponse)

        LoadAllProviders()

        If String.IsNullOrEmpty(info.PageTrace) Then
            Return info.PageContent
        End If

        return info.PageContent & _
            _provider.PostProcess(info.PageTrace)
    End Function

    Private Shared Sub LoadAllProviders()
        If Not _provider Is Nothing Then Return
        SyncLock _internalLock
            If Not _provider Is Nothing Then Return

            Dim name As String = "system.web/traceOutputService"
            Dim section As TraceOutputServiceSection = _
                DirectCast(WebConfigurationManager.GetSection(name), _
                TraceOutputServiceSection)

            _providers = New TraceOutputProviderCollection()
            ProvidersHelper.InstantiateProviders(section.Providers, _
                _providers, GetType(TraceOutputProvider))

            _provider = _providers(section.DefaultProvider)
            If _provider Is Nothing Then
                Throw New ProviderException(
                    "Couldn’t load the default TraceOutputProvider")
            End If
        End SyncLock
    End Sub

    Private Shared Function SplitPageResponse( _
            ByVal pageResponse As String) As PageOutputInfo
        Dim info As New PageOutputInfo

        Dim beginWith As String = "<div id=""__asptrace"">"
        Dim pos As Integer = pageResponse.IndexOf(beginWith)
        If (pos < 0) Then
            info.PageContent = pageResponse
            info.PageTrace = String.Empty
            Return info
        End If

        info.PageTrace = pageResponse.Substring(pos)
        info.PageContent = pageResponse.Substring(0, pos - 1)
        Return info
    End Function
End Class
You populate the Providers collection and then retrieve the current provider object by name, as shown here:
_providers = New TraceOutputProviderCollection()
ProvidersHelper.InstantiateProviders(section.Providers, _
    _providers, GetType(TraceOutputProvider))
_provider = _providers(section.DefaultProvider)
The TraceOutputProviderCollection class inherits from a system-provided class, called ProviderCollection. ProvidersHelper is a built-in helper class located in the System.Web.Configuration namespace. It features two essential methods—InstantiateProviders and InstantiateProvider. The former creates instances of all registered providers; the latter creates the instance of a particular provider. Both methods retrieve from the configuration section the details of the providers or provider to be created. These methods relieve you of most of the burden involved in instantiating an ASP.NET provider. These classes, in fact, are extensively used within the built-in implementation of the provider model. The InstantiateXXX methods end up calling into Activator.CreateInstance to get an instance of the specified type.

Wrapping the Standard Tracer
Now that you have a functional infrastructure for a provider-based trace service, it is time to start adding a few sample providers. The first provider you should try is one that simply mirrors the standard output, adding a signature to signify that the new infrastructure is being used.
The StandardTraceOutputProvider inherits from TraceOutputProvider and overrides the Initialize and PostProcess methods. In the Initialize method StandardTraceOutputProvider doesn't do much more than invoke the base method and read the application name, if any, from the configuration. (Check the source code in this month's download for more details.)
Public Overrides Function PostProcess( _
       ByVal tracedText As String) As String
    Return GetTopSignature() & tracedText & GetBottomSignature()
End Function
You register the trace provider with a script like so:
<traceOutputService defaultProvider="StandardTraceOutputProvider">
   <providers>
      <add name="StandardTraceOutputProvider"
           type="MsdnMag.StandardTraceOutputProvider, 
           MyTracer" 
      />
   </providers>
</traceOutputService>
At this point, add any pages to a sample ASP.NET application and turn on page output tracing by setting Trace=true in either the @Page directive or in web.config.

A Richer ASP.NET Tracer
Now let's craft a richer trace provider that will display only a selected subset of sections (that is, only Trace Information) and will include collapse/expand functionality that makes reading data easier.
To accomplish this, you need to map the traced text to an internal representation—a list of custom TraceBlock structures—and, based on the configuration, decide which ones to insert in the returned stream. In addition, you'll emit a bit of JavaScript code in each block used to hide/display the block on demand. Figure 9 shows the final result.
Figure 9 TraceProvider with Collapse/Expand Functionality 
The original trace text is a collection of <table> elements, each representing a section (Request Details, Trace Information, and so on). The PostProcess method of the RichTraceOutputProvider object walks through the original text and populates a list of TraceBlock objects. Here's what a TraceBlock looks like in C#:
internal class TraceBlock
{
    public TraceSections Code;
    public string Name;
    public string ID;
    public string Body;
}
Here's TraceBlock in Visual Basic:
Friend Class TraceBlock
  Public Code As TraceSections
  Public Name As String
  Public ID As String
  Public Body As String
End Class
The Code field indicates the type of the section from the TraceSections enum (see the source code in this month's code download). The Name field is the title of the section. The ID field is the name-based ID used to script the section. And the Body field contains the original text for the section block.
The provider also defines a couple of new virtual properties: Sections and CollapseAll. The former is a TraceSections value that indicates which traced sections should be displayed to the user. The CollapseAll property is a Boolean value that indicates whether the sections should be initially displayed in the collapsed state. The trace output in Figure 9 originated from this configuration script:
<add name="RichTraceOutputProvider"
     type="MsdnMag.CuttingEdge.RichTraceOutputProvider, MyTracer"
     sections="RequestDetails,TraceInformation,SessionState" 
     collapseAll="true" />
The original HTML text for each section is prefixed with a client button. If the button is visible, the table with trace data is hidden and vice-versa. Both the button and the table are bound to a click event that toggles visibility on and off. The button markup and any necessary script code is added in the PostProcess method of the provider. The following script code is associated with each button and table:
function Toggle(showInfo, tableName, buttonName) {
   var table = document.getElementById(tableName);
   var button = document.getElementById(buttonName);
   if (showInfo) {
    table.style["display"] = 'none';
    button.style["display"] = '';
   } else {
    table.style["display"] = '';
    button.style["display"] = 'none';
   }
}
By default, buttons are hidden and tables are shown. By setting CollapseAll to true, you get the opposite. As in Figure 9, to expand a collapsed trace section you click the button; to collapse an expanded section, you click anywhere in the table.
Note that the preceding script works with the Mozilla Firefox and Netscape 7 browsers, as well as with Internet Explorer.
ASP.NET tracing is a powerful feature that lets developers audit the run-time behavior of pages and applications. In this column I demonstrated how to build a non-obtrusive and easy-to-use infrastructure to capture the standard traced text and either replace or modify it before display.

Send your questions and comments for Dino to  cutting@microsoft.com.


Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.

Page view tracker