Information the Debugger Needs

Instrumenting Web Services

Although source-level debugging is very powerful for debugging applications, in plenty of situations it is not practical. For example, if you interactively debug an ASP.NET Web service, you effectively block all threads from servicing other requests. This is not very practical if the Web service is being hosted in a production environment and you have no ability to isolate it.

In such situations, instrumentation can be invaluable. Instrumentation is the process of generating output directed at the developer or administrator that provides information about the running state of your Web service.

The .NET Framework offers developers many options for instrumenting Web services and the applications that consume them. In this section, I cover three techniques that you can use to instrument your Web service: tracing, the Event Log, and performance counters.

Tracing

Tracing is the process of recording key events during the execution of an application over a discrete period of time. This information can help you understand the code path taken within the application. Tracing information can also contain information about the changes made to the state of the application.

Different levels of tracing are often needed during different phases of a product's lifecycle. For example, during development, the information might be quite verbose. But when the application ships, only a subset of that information might be useful.

The System.Diagnostics namespace contains the Debug and Trace classes, which provide a straightforward means of outputting tracing information from your application. These two classes exhibit similar behavior. In fact, internally they both forward their calls to corresponding static methods exposed by the private TraceInternal class. The primary difference between them is that the Debug class is intended for use during development and the Trace class is intended for use throughout the lifecycle of the application.

Table 11-2 describes the properties and methods exposed by the Debug and Trace classes. I discuss most of the properties and methods in greater detail later in this section.

Table 11-2  Properties and Methods of the Debug and Trace Classes

Property

Description

AutoFlush

Specifies whether the Flush method should be called after every write

IndentLevel

Specifies the level of indentation for writes

IndentSize

Specifies the number of spaces of a single indent

Listeners

Specifies the collection of listeners that monitor the debug output

Method

Description

Assert

Evaluates an expression and then displays the call stack and an optional user-defined message in a message box if the expression is false

Close

Flushes the output buffer and then closes the listener

Fail

Displays the call stack and a user-defined message in a message box

Flush

Flushes the output buffer to the collection of listeners

Indent

Increases the value of the IndentLevel property by one

Unindent

Decreases the value of the IndentLevel property by one

Write

Writes information to the collection of listeners

WriteLine

Writes information and a linefeed to the collection of listeners

WriteLineIf

Writes information and a linefeed to the collection of listeners if an expression evaluates to true

Each of the static methods exposed by the Debug and Trace classes is decorated with the Conditional attribute. This attribute controls whether a call made to a particular method is executed based on the presence of a particular preprocessing symbol.

The methods exposed by the Debug class are executed only if the DEBUG symbol is defined. The methods exposed by the Trace class are executed only if the TRACE symbol is defined.

You define symbols at compile time; you can define them within the source code or using a compiler switch. The compiler will generate MSIL to call a method decorated with the Conditional attribute only if the required symbol is defined. For example, a call to Debug.WriteLine will not be compiled into MSIL unless the DEBUG symbol is defined.

With Visual C#, you can use the #define directive to define a symbol scoped to a particular file. For example, the following code defines both the DEBUG and TRACE symbols:

#define DEBUG #define TRACE

You can also define a symbol using the Visual C# compiler /define switch. Symbols defined in this manner are scoped to all the source code files compiled into the executable. The following command defines the DEBUG and TRACE symbols at compile time:

csc /define:DEBUG;TRACE /target:library MyWebServiceImpl.cs

In general, the DEBUG and TRACE symbols are defined when you compile debug builds, and only the TRACE symbol is defined when you compile release builds. This is the default in Visual Studio .NET. You can change which symbols are defined at compile time by configuring the project settings under Configuration Properties, Build, and then Conditional Compilation Constants.

Now that you know how to set the appropriate symbols, let's look at how to use of some of the key methods exposed by the Debug and Trace classes.

Asserting Errors

Developers often have to strike a balance between writing robust code and maximizing an application's performance. In an effort to write robust code, they often find themselves writing a considerable amount of code that evaluates the state of the application.

Rich validation code can be invaluable for tracking down issues quickly during development, but an overabundance of validation code can affect the application's performance. In general, publicly exposed Web services should validate the input parameters received from the client. But in certain situations it is not necessary to validate member variables that are considered implementation details of the Web service.

In cases where it makes sense to perform validation only during development, you can use the Assert method exposed by the Debug and Trace classes. This method evaluates an expression, and if the expression evaluates to false, it returns information about the assertion. The error information includes text defined by the application as well as a dump of the call stack.

The ability to programmatically generate error information that includes a dump of the call stack is quite handy. There might be certain places in your code where you always want to do this. For these situations, you can call the Fail method of the Debug and Trace classes. Calling Fail is the equivalent of calling Assert where the expression always evaluates to false.

Let's take a look at an example. The following code demonstrates the use of the Assert and Fail methods:

#define DEBUG using System.Web.Services; using System.Diagnostics; public class Insurance {     [WebMethod]     public double CalculateRate(int age, bool smoker)     {         StreamReader stream = File.OpenText("RateTable.txt");         Debug.Assert((stream.Peak() == -1),          "Error reading the rate table.",          "The rate table appears to be empty.");         try         {             // Implementation...         }         catch(Exception e)         {             Debug.Fail("Unhandled exception.");             throw;         }     } }

The code generates an assertion if the RateTable.txt file is empty or if an unhandled exception is caught.

Because the Assert and Fail methods are called within a Web service, there is an issue with the default behavior of these methods. By default, the Assert and Fail methods display dialog boxes if the expression evaluates to false. But this is obviously not practical for server-side code. You can alter the web.config file to redirect the output to a log file, as shown here:

<configuration>   <system.diagnostics>     <assert assertuienabled="false"      logfilename="c:\Logs\Assert.log"/>   </system.diagnostics>   <!-- The rest of the configuration information... --> </configuration>

This portion of the web.config file specifies an assert element to alter the default behavior of the Assert and Fail methods. First I set the assertuienabled attribute to false to specify that an assertion should not result in the display of a modal dialog box. I then specify the file where the asserts will be written using the logfilename attribute. I also need to create the Logs directory and give the ASPNET user sufficient permissions to create and write to the Assert.log file because, by default, the ASPNET user does not have permissions to write to the file system.

Finally, note that the default behavior of the Assert and Trace methods is to ignore the error and continue. For this reason, do not use the Assert and Fail methods as a substitute for throwing an exception.

Conditional Preprocessor Directives

Recall that the Conditional attribute provides a means of defining methods that should be called only if a particular preprocessing symbol is defined. However, at times you might want to have finer-grained control over implementation that is compiled into an application when a particular preprocessing symbol is defined. For example, you might want to have extended test routines embedded within your code during development. You can gain this finer-grained control by specifying conditional preprocessor directives within your application.

Conditional preprocessor directives mark blocks of code that will be compiled into MSIL only if a particular symbol is defined. Table 11-3 describes the key conditional preprocessor directives used to do this.

Table 11-3  Conditional Preprocessor Directives

Directive

Description

#if

Begins a conditional compilation block. Code following the #if directive will be compiled only if the condition evaluates to true.

#else

Specifies statements that should be compiled only if the condition specified by the #if directive evaluates to false.

#endif

Terminates a conditional compilation block.

#define

Defines a preprocessing symbol.

#undef

Negates the definition of a preprocessing symbol.

For public Web services, there is rarely a good reason to return a stack trace to the user in the event of an exception. A stack trace offers minimal benefit to an external user of your Web service, plus the information provided by the stack trace can be used against you to probe for security vulnerabilities within your Web service. During development, however, this additional information can be helpful for debugging.

The following example uses conditional preprocessor directives to return stack trace information only if the application was compiled with the DEBUG symbol defined:

#define DEBUG using System.Web.Services; using System.Web.Services.Protocols; public class Insurance {     [WebMethod]     public double CalculateRate(int age, bool smoker)     {         try         {             // Implementation...         }         catch(Exception e)         { #if DEBUG             throw new SoapException             ("An unhandled exception was encountered.",              SoapException.ServerFaultCode, e); #else             throw new SoapException             ("An unhandled exception was encountered.",              SoapException.ServerFaultCode); #endif         }         // Implementation...     } }

The example throws a SoapException if an unhandled exception is caught. The data returned within the SoapException depends on whether the DEBUG symbol is defined. If the DEBUG symbol is defined, a new instance of the SoapException class is initialized with the caught exception. If the DEBUG symbol is not defined, a new instance of the class is initialized with only a generic error message.

Trace Log

So far, I have focused mostly on error conditions. However, instrumenting normal operations of an application can be equally valuable. The Debug and Trace classes provide a set of methods and properties for logging tracing information within your application.

Output is written to the log using the Write, WriteLine, and WriteLineIf methods. The Write method outputs text to the trace log, and WriteLine outputs text followed by a linefeed. If text should be written to the trace log only if a certain condition is met, you can use the WriteLineIf method.

The Debug and Trace classes also expose properties and methods to control the format of the output. You can use the IndentLevel property to set the number of times a new line of text is indented. The Indent and Unindent methods increment and decrement the IndentLevel property, respectively. The IndentSize property specifies the number of spaces in an indent.

You can specify when the output buffer will be flushed to the trace log by calling the Flush method. You can also set the AutoFlush property to true to cause the output buffer to be flushed after every write to the trace log.

Recall that the Debug and Trace classes defer their implementation to the TraceInternal class. Therefore, modifying the static variables using one class affects the other. For example, setting Debug.IndentSize to 4 also affects the indent size of the Trace class.

The following example shows the use of the trace methods within the context of a Web service:

#define TRACE using System.Diagnostics; using System.Web.Services; public class WebService {     public WebService()     {         Trace.IndentSize = 4;         Trace.AutoFlush = true;     }     [WebMethod]     public void MyWebMethod(string param)     {         Trace.WriteLine("MyWebMethod");         Trace.Indent();         Trace.WriteLine("Start: " + DateTime.Now);         // Implementation...         Trace.WriteLine("End: " + DateTime.Now);         Trace.Unindent();     } }

Both the IndentSize and AutoFlush properties are set within the constructor of the method. You can also set them at run time within the web.config file, as shown here:

<configuration>   <system.diagnostics>     <trace autoflush="true" indentsize="0"/>   </system.diagnostics> </configuration>

You can use the trace element to set the initial value of the AutoFlush and IndentSize properties. Any changes made to these properties by the application will override these default settings.

You should be aware of one issue when you call the WriteLineIf method. Consider the following code fragment:

Trace.WriteLineIf(someCondition, "Some error message.",  someLargeObject.ToString());

Because the text that will be written to the logs is passed to the WriteLineIf method, the someLargeObject object must be serialized to a string even if the condition evaluates to false. To avoid unnecessary processing, we can rewrite the code as follows:

#if TRACE if(someCondition) {     Trace.WriteLine("Some error message.",      someLargeObject.ToString()); } #endif

The someLargeObject object will be serialized to a string only if the someCondition variable is equal to true. This ensures that the costs associated with serializing someLargeObject are incurred only if the resulting text will be written to the trace log.

Trace Listeners

The Debug and Trace classes support outputting the tracing log to multiple listeners. A listener must inherit from the TraceListener class. The .NET Framework provides three listeners: DefaultTraceListener, EventLogTraceListener, and TextWriterTraceListener.

The DefaultTraceListener is added to the collection of listeners by default. It generates output that can be captured by debuggers for managed and unmanaged code. The tracing information is sent to managed code debuggers via the Debugger.Log method and to unmanaged code debuggers by means of the OutputDebugString Win32 API. In the case of Visual Studio .NET, the output is displayed in the Output window.

You can add or remove listeners using the Listeners property of the Debug and Trace classes. The following example removes the instance of the DefaultTraceListener and adds an instance of the TextWriterTraceListener to the collection of listeners:

// Remove instance of the DefaultTraceListener. Debug.Listeners.Remove(Debug.Listeners[0]); // Add instance of the TextWriterTraceListener. System.IO.FileStream fs =  System.IO.File.OpenWrite(@"c:\Logs\Tracing.log"); Debug.Listeners.Add(new TextWriterTraceListener(fs));

You can also add or remove listeners at run time. The following example performs the same task as the previous code, but by modifying the web.config file, as you see here:

<configuration>   <system.diagnostics>     <trace>       <listeners>         <add name="Text"          type="System.Diagnostics.TextWriterTraceListener,System"          initializeData="c:\Logs\Tracing.log"/>         <remove type="System.Diagnostics.DefaultTraceListener,System"/>        </listeners>     </trace>   </system.diagnostics> </configuration>

In either case, you will need to create the Logs directory and give the ASPNET user sufficient permissions to create and write to the Tracing.log file because, by default, the ASPNET user does not have permissions to write to the file system.

Trace Switches

The DEBUG and TRACE preprocessing symbols allow you to configure the level of tracing generated by an application at compile time. However, sometimes you might need finer-grained levels of tracing or you might need to change the level of tracing at run time. For example, you might want to record errors and warnings only under normal operating conditions, but when an issue arises, you might want to enable more verbose tracing without having to recompile the code.

You can achieve this functionality by leveraging classes that inherit from the Switch class within your code. The .NET Framework includes two such classes, BooleanSwitch and TraceSwitch. Much like the preprocessing symbols, the BooleanSwitch class provides a mechanism to indicate whether tracing should be enabled. However, you can indicate this at run time by modifying the application configuration file.

For example, suppose I want to create an instance of the BooleanSwitch class that allows me to control whether trace information is displayed about when the beginning and ending of a method is reached.

using System; using System.Diagnostics; public class Application {     private static BooleanSwitch profileMethodsSwitch =      new BooleanSwitch("ProfileMethods", "Controls whether      start and end times are displayed for each method.");     static public void Main(string[] args)     {         Application.DoSomething("test", 3);     }     private void DoSomething(string param1, int param2)     {         Trace.WriteLineIf(profileMethodsSwitch.Enabled,          "Start DoSomething:  "  +  DateTime.Now);         // Implementation...         Trace.WriteLineIf(profileMethodsSwitch.Enabled,          "End DoSomething:  "  +  DateTime.Now);     } }

I define a BooleanSwitch to determine whether method-profiling information should be written to the tracing log. First I create a static variable of type BooleanSwitch and define the name and description of the switch within the constructor. When the switch's constructor is called, it will read the application configuration file to determine its value (true or false).

Next I use profileMethodsSwitch as the condition of the calls to WriteLineIf that display method profile information. Notice that this switch can be used by the WriteLineIf method of both the Trace and Debug classes. For that matter, the switch can be specified by any conditional statement within the application.

Once the switch has been defined, you can configure it within the application's configuration file. The configuration file shown on the next page enables the ProfileMethods switch.

<configuration>   <system.diagnostics>     <switches>       <add name="TraceMethods" value="1" />     </switches>   </system.diagnostics> </configuration>

I enable the TraceMethods switch by specifying an add element with its name attribute set to the same string used to initialize the constructor of the switch. If the TraceMethods switch is not listed in the configuration file, the default value will be 0 or false.

If you want to achieve more granularity when you configure which tracing information to display, you can use the TraceSwitch class. You can set an instance of a TraceSwitch class to a numeric value to indicate the level of tracing information that should be displayed.

The TraceSwitch class supports five levels of tracing, from 0 through 4. Table 11-4 describes these tracing levels.

Table 11-4   Properties and Their Associated Tracing Levels

Property

Tracing Level

Description

N/A

0

Tracing is turned off.

TraceError

1

Error messages only.

TraceWarning

2

Warning and error messages.

TraceInfo

3

Informational, warning, and error messages.

TraceVerbose

4

Verbose.

Setting an instance of the TraceSwitch class to a particular value is cumulative. For example, if the value is set to 3, not only is TraceInfo enabled, but TraceWarning and TraceError are enabled as well.

Event Log

Some tracing information should be recorded regardless of switch settings or what preprocessing symbols are defined. You should trace, for example, a critical error encountered by a Web service that needs the immediate attention of a system administrator.

Critical information about the execution of an application should be written to the Event Log. The Event Log provides a common repository for storing events from multiple sources. By default, the system has three logs: the Application Log, the Security Log, and the System Log. Events raised by your application should typically be posted to the Application Log.

Because the Event Log is an infrastructure component provided by the operating system, it comes with supporting infrastructure that you would otherwise have to create yourself. For example, an Event Log Service will automatically control the size of individual logs so that you never have to truncate the log yourself. You can use the Event Log Viewer to view and sort the entries in the log. You can also obtain additional tools that operate on the Event Log and perform such tasks as notifying the system administrator in the event of an application failure.

You can use the EventLog class to post messages to the Event Log. But before you write to the Event Log, you must first register an event source. The event source is usually associated with your application. The following code shows how to register an event source:

if(! EventLog.SourceExists("My Web Service")) {     EventLog.CreateEventSource("My Web Service", "Application"); }

The preceding code first determines whether a particular event source is already registered. If it is not, the code will register it. You can then write entries to the Event Log, as shown in this code:

EventLog.WriteEntry("My Web Service",  "Unable to connect to the database", EventLogEntryType.Error);

This code writes a warning event to the Application Log. The three categories of events are errors, warnings, and informational events. You can also include additional information with the event, including an application-defined event ID and category ID as well as raw binary data that can be helpful when you try to diagnose the problem.

By default, the ASPNET user does not have permissions to write to the event log. To provide these permissions, set the \\HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\Eventlog\Application\RestrictGuestAccess registry key to 0 and reboot the machine.

By default, the ASPNET user also does not have permissions to create event sources. You can overcome this limitation by registering the event source as part of the installation procedure of your Web service. If you want to register event sources at run time, you need to grant the ASPNET user read/write permissions to the \\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ Services\Eventlog registry key as well as all of its subkeys.

The EventLog class also supports additional functionality such as receiving notification when a new entry is created. Table 11-5 describes the properties, methods, and event exposed by the EventLog class.

Table 11-5   Class Properties, Methods, and Event

Property

Description

EnableRaisingEvents

Specifies whether the instance of the EventLog class will receive EntryWritten event notifications

Entries

Retrieves a collection of instances of the EventLog Entry class

Log

Specifies the name of the event log that will be accessed

LogDisplayName

Retrieves the friendly name of the event log

MachineName

Specifies the name of the machine where the targeted event log resides

Source

Specifies the name of the source of the events written to the event log

Method

Description

Clear

Removes all entries from the targeted event log

Close

Closes the handle to the event log

CreateEventSource

Registers a new event source within the system registry

Delete

Deletes the specified event log

DeleteEventSource

Unregisters a new event source

Exists

Indicates whether the specified event log exists

GetEventLogs

Retrieves an array of EventLog objects from the targeted machine

LogNameFromSourceName

Retrieves the name of the Event Log associated with a particular event source

SourceExists

Indicates whether the specified event source is registered

WriteEntry

Writes an entry to the event log

Event

Description

EntryWritten

Fires when an event is written to the Event Log on the local machine

Performance Counters

So far, I have limited my discussion of the methods of instrumentation to asynchronous forms of communication. The application writes data to a text file or the Event Log, and then the client opens the information source and reads the data. However, at times the client might need to monitor the state of the application in real time.

For example, suppose I develop a Web service that accepts purchase orders from my customers. I might be interested in knowing the number of requests per second that my Web service receives. Information such as this can be communicated using performance counters.

As you probably know, many applications publish a lot of data using performance counters. ASP.NET is no exception. It publishes numerous counters about its run-time state, including the number of applications currently running and the number of worker processes running.

ASP.NET also publishes numerous counters about the run-time state of individual applications that it is hosting. These counters include the number of requests per second, the number of requests queued, and the average request execution time.

If the Web service I just described accepts only purchase orders, I can monitor the number of requests received per second without writing a single line of code. I can simply use an application that ships with Windows called Performance Monitor. (The steps required to launch Performance Monitor vary depending on your operating system, so consult online help.)

With Performance Monitor running, you can add counters that you want to have charted. First click the button with the plus sign to open the Add Counters dialog box. Select ASP.NET Applications in the Performance Object drop-down list, and then select the Requests/Sec counter. Then select the instance that corresponds to the application you want to monitor. The name of the application will be associated with the name of the directory in which the application lives.

The Add Counters dialog box should look similar to this:

You can also create your own performance counters by using the Per formanceCounterCategory and the PerformanceCounter classes. The following example shows how to use the PerformanceCounterCategory class to register a new performance counter:

if(! PerformanceCounterCategory.Exists("My Web Service")) {     PerformanceCounterCategory.Create("My Web Service",      "Performance counters published by My Web Service.",      "Total Purchase Orders Processed",      "The total number of purchase orders processed."); }

The preceding code registers a category called My Web Service and a counter called Total Purchase Orders Processed if the category does not already exist.

After the counter is registered, you can publish to it using an instance of the PerformanceCounter class. The following code creates a performance counter object and increments the counter by one:

PerformanceCounter processedPOs =  new PerformanceCounter("My Web Service",  "Total Purchase Orders Processed", false); processedPOs.Increment();

I create an instance of the PerformanceCounter class and initialize it to enable writes to the Total Purchase Orders Processed counter. I then increment the counter by 1 by invoking the object's Increment method.

This is fine, but my goal is to publish the average number of purchase orders processed per second. If my Web service exposes more than one Web method, I will not be able to leverage the Requests/Sec counter exposed by ASP.NET to achieve my purpose. I need to create another custom counter.

To create this new custom counter, I must leverage the CounterCreationData class to register the counter. This class allows me to set the type of counter I need. The following example registers counters to monitor total purchase orders processed as well as the amount processed per second:

if(! PerformanceCounterCategory.Exists("My Web Service")) {     CounterCreationDataCollection counterCDC =      new CounterCreationDataCollection();     counterCDC.Add(new CounterCreationData     ("Purchase Orders Processed/sec",      " The number of purchase orders processed per second.",      PerformanceCounterType.RateOfCountsPerSecond32));     counterCDC.Add(new CounterCreationData     ("Total Purchase Orders Processed",      " The total number of purchase orders processed.",      PerformanceCounterType.NumberOfItems32));     PerformanceCounterCategory.Create("My Web Service",      " Performance counters published by My Web Service.",      counterCDC); }

First I create an instance of the CounterCreationDataCollection class that will be used to pass the counters I want to register. I then create two instances of the CounterCreationData class to register the counters. Notice that I do not have to write any code to calculate the average number of purchase order requests per second. This is handled for me by the Performance Monitor.

By default, the ASPNET user has permissions to write to a particular performance counter but not to create performance counters and categories. You can overcome this limitation by registering the performance counters as part of the installation procedure of your Web service.

Sometimes you might want to create performance counters at run time. For example, you might want to associate an instance of a performance counter with a particular instance of your Web service or possibly with a particular user. In order to register a performance counter at run time, you need to grant the ASPNET user read/write permissions to the \\HKEY_LOCAL_MACHINE\SOFTWARE\ Microsoft\Windows NT\CurrentVersion\Perflib registry key as well as all of its subkeys.

Tables 11-6, 11-7, and 11-8 describe the properties and methods exposed by the CounterCreationData, PerformanceCounter, and PerformanceCounterCategory classes, respectively.

Table 11-6  CounterCreationData Class Properties

Property

Description

CounterHelp

Specifies the help string that describes the counter

CounterName

Specifies the name of the counter

CounterType

Specifies the type of counter

Table 11-7  PerformanceCounter Class Properties and Methods

Property

Description

CategoryName

Specifies the name of the category in which the counter is registered

CounterHelp

Retrieves the counter help text

CounterName

Specifies the name of the counter

CounterType

Retrieves the type of the counter

InstanceName

Specifies the name of the instance with which the counter is associated

MachineName

Specifies the name of the machine with which the counter is associated

RawValue

Specifies the uncalculated value of this counter

ReadOnly

Specifies whether the counter is read-only

Method

Description

BeginInit

Used by Visual Studio .NET to start the initialization of a counter

Close

Closes the counters and releases any acquired resources

Decrement

Decrements the counter by one within an atomic operation

EndInit

Used by Visual Studio .NET to end the initialization of a counter

Increment

Increments the counter by one within an atomic operation

IncrementBy

Increments the counter by the specified value within an atomic operation

NextSample

Retrieves the uncalculated value of a counter sample

NextValue

Retrieves the calculated value of a counter sample

RemoveInstance

Removes the category instance associated with the counter

Table 11-8  PerformanceCounterCategory Class Properties and Methods

Property

Description

CategoryHelp

Retrieves the category help text

CategoryName

Specifies the name of the category

MachineName

Specifies the machine on which the category exists

Method

Description

CounterExists

Indicates whether a specific counter is registered under a particular category

Create

Registers a category and one or more counters

Delete

Deletes the category and its registered counters

Exists

Indicates whether a particular category is registered

GetCategories

Retrieves the list of registered categories

GetCounters

Retrieves the list of registered counters for the particular category

GetInstanceNames

Retrieves the list of instances for a particular category

InstanceExists

Indicates whether a particular instance of the category is registered

ReadCategory

Gets the instance data associated with each counter registered under the category



Building XML Web Services for the Microsoft  .NET Platform
Building XML Web Services for the Microsoft .NET Platform
ISBN: 0735614067
EAN: 2147483647
Year: 2002
Pages: 94
Authors: Scott Short

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net