This section explores the following: What you can configure using the XML base configuration files How you can redirect a strong named referenced assembly to a different version How you
Trang 1PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=msilehepg, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilehepgdat, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilehExtCOM, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilehexthost, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilehRecObj, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilehshell, Version=6.0.6000.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msilEventViewer, Version=6.0.0.0, Culture=Neutral,
PublicKeyToken=31bf3856ad364e35, processorArchitecture=msil
If the security of the system changes, it ’ s not sure if the native image has the security requirements it needs for running the application This is why the native images become invalid with a system configuration change With the command ngen update all native images are rebuilt to include the new configurations
Installing CLR 2.0 runtime also installs the Native Image Service (or the Window Service CLR Optimization Service), with the name Microsoft NET Framework NGEN v2.0.50727_X86 This service can be used to defer compilation of native images and regenerates native images that have been invalidated
The command ngen install myassembly /queue can be used by an installation program to defer compilation of myassembly to a native image using the Native Image Service ngen update /queue regenerates all native images that have been invalidated With the ngen queue options pause ,
continue , and status you can control the service and get status information
You might ask why the native images cannot be created on the developer system, and you just distribute the native image to the production system The reason is that the native image generator takes care of the CPU that is installed with the target system and compiles the code optimized for the CPU type During installation of the application, the CPU is known
Configuring NET Applications
COM components used the registry to configure components Configuration of NET applications is done by using configuration files With registry configurations, an xcopy deployment is not possible
Configuration files can simply be copied The configuration files use XML syntax to specify startup and runtime settings for applications
This section explores the following:
What you can configure using the XML base configuration files How you can redirect a strong named referenced assembly to a different version How you can specify the directory of assemblies to find private assemblies in subdirectories and shared assemblies in common directories or on a server
❑
❑
❑
Trang 2Configuration Categories
The configuration can be grouped into these categories:
Startup settings enable you to specify the version of the required runtime It ’ s possible that
different versions of the runtime could be installed on the same system The version of the
runtime can be specified with the < startup > element
Runtime settings enable you to specify how garbage collection is performed by the runtime,
and how the binding to assemblies works You can also specify the version policy and the code
base with these settings You take a more detailed look into the runtime settings later in this
chapter
WCF settings are used to configure applications using WCF You deal with these configurations
in Chapter 42 , “ Windows Communication Foundation ”
Security settings are introduced in Chapter 20 , “ Security, ” and configuration for cryptography
and permissions is done there
These settings can be provided in three types of configuration files:
Application configuration files include specific settings for an application, such as binding
information to assemblies, configuration for remote objects, and so on Such a configuration file
is placed into the same directory as the executable; it has the same name as the executable with a
.config extension appended ASP.NET configuration files are named web.config
Machine configuration files are used for system - wide configurations You can also specify assembly
binding and remoting configurations here During a binding process, the machine configuration file
is consulted before the application configuration file The application configuration can override
settings from the machine configuration The application configuration file should be the preferred
place for application - specific settings so that the machine configuration file stays smaller and more
manageable A machine configuration file is located in %runtime_install_path%\config\
Machine.config
Publisher policy files can be used by a component creator to specify that a shared assembly is
compatible with older versions If a new assembly version just fixes a bug of a shared
component, it is not necessary to put application configuration files in every application
directory that uses this component; the publisher can mark it as compatible by adding a
publisher policy file instead In case the component doesn ’ t work with all applications, it is
possible to override the publisher policy setting in an application configuration file In contrast
to the other configuration files, publisher policy files are stored in the GAC
How are these configuration files used? How a client finds an assembly (also called binding ) depends on
whether the assembly is private or shared Private assemblies must be in the directory of the application
or in a subdirectory thereof A process called probing is used to find such an assembly If the assembly
doesn ’ t have a strong name, the version number is not used with probing
Shared assemblies can be installed in the GAC or placed in a directory, on a network share, or on a Web
site You specify such a directory with the configuration of the codeBase shortly The public key, version,
and culture are all important aspects when binding to a shared assembly The reference of the required
assembly is recorded in the manifest of the client assembly, including the name, the version, and the
public key token All configuration files are checked to apply the correct version policy The GAC and
code bases specified in the configuration files are checked, followed by the application directories,
and probing rules are then applied
Trang 3Configuring Directories for Assembly Searches
You ’ ve already seen how to install a shared assembly to the GAC Instead of installing a shared assembly
to the GAC, you can configure a specific shared directory by using configuration files This feature can
be used if you want to make the shared components available on a server Another possible scenario arises if you want to share an assembly between your applications, but you don ’ t want to make it publicly available in the GAC, so you put it into a shared directory instead
There are two ways to find the correct directory for an assembly: the codeBase element in an XML configuration file, or through probing The codeBase configuration is available only for shared assemblies, and probing is done for private assemblies
< codeBase >
The < codeBase > can also be configured using the NET Configuration utility Code bases can be configured by selecting the properties of the configured application, SimpleShared , inside the Configured Assemblies in the Applications tree Similarly to the Binding Policy, you can configure lists of versions with the Codebases tab Figure 17 - 17 shows that the version 1.1 should be loaded from the Web server
http://www.christiannagel.com/WroxUtils
Figure 17 - 17 The NET Configuration utility creates this application configuration file:
Trang 4< /dependentAssembly >
< /assemblyBinding >
< /runtime >
< /configuration >
The < codeBase > element has the attributes version and href With version , the original referenced
version of the assembly must be specified With href , you can define the directory from where the
assembly should be loaded In the example, a path using the HTTP protocol is used A directory on a
local system or a share is specified using href= “ file:C:/WroxUtils ”
Using that assembly loaded from the network causes a System.Security.Permissions exception
to occur You must configure the required permissions for assemblies loaded from the network In
Chapter 20 , “ Security, ” you learn how to configure security for assemblies
< probing >
When the < codeBase > is not configured and the assembly is not stored in the GAC, the runtime tries to
find an assembly through probing The NET runtime tries to find assemblies with either a dll or an
.exe file extension in the application directory, or in one of its subdirectories, that has the same name as
the assembly searched for If the assembly is not found here, the search continues You can configure
search directories with the < probing > element in the < runtime > section of application configuration
files This XML configuration can also be done easily by selecting the properties of the application with
the NET Framework Configuration tool You can configure the directories where the probing should
occur by using the search path in the NET Framework configuration (see Figure 17 - 18 )
Trang 5A solution to this dilemma could be an architecture that allows installation of different versions of shared components, with clients using the version that they referenced during the build process This solves a lot of problems but not all of them What happens if you detect a bug in a component that ’ s referenced from the client? You would like to update this component and make sure that the client uses the new version instead of the version that was referenced during the build process
Therefore, depending on the type in the fix of the new version, you sometimes want to use a newer version, and you also want to use the older referenced version as well The NET architecture enables both scenarios
In NET, the original referenced assembly is used by default You can redirect the reference to a different version using configuration files Versioning plays a key role in the binding architecture — how the client gets the right assembly where the components live
Version Numbers
Assemblies have a four - part version number, for example, 1.1.400.3300 The parts are
< Major > < Minor > < Build > < Revision > How these numbers are used depends on your application configuration
A good policy is to change the major or minor number on changes incompatible with the previous version, but just the build or revision number with compatible changes This way, it can be assumed that redirecting an assembly to a new version where just the build and revision changed is safe
With Visual Studio 2008, you can define the version number of the assembly with the assembly information in the project settings The project settings write the assembly attribute
[AssemblyVersion] to the file AssemblyInfo.cs :
[assembly: AssemblyVersion(“1.0.0.0”)]
Trang 6Instead of defining all four version numbers you can also place an asterisk in the third or fourth place:
[assembly: AssemblyVersion(“1.0.*”)]
With this setting, the first two numbers specify the major and minor version, and the asterisk ( * ) means
that the build and revision numbers are auto - generated The build number is the number of days since
January 1, 2000, and the revision is the number of seconds since midnight divided by two Though the
automatic versioning might help during development time, before shipping it is a good practice to
define a specific version number
This version is stored in the assembly section of the manifest
Referencing the assembly in the client application stores the version of the referenced assembly in the
manifest of the client application
Getting the Version Programmatically
To make it possible to check the version of the assembly that is used from the client application, add the
method GetAssemblyFullName() to the SharedDemo class created earlier to return the strong name of
the assembly For easy use of the Assembly class, you have to import the System.Reflection
The FullName property of the Assembly class holds the name of the class, the version, the locality,
and the public key token, as you see in the following output, when calling GetAssemblyFullName() in
your client application
In the client application, just add a call to GetAssemblyFullName() in the Main() method after
creating the shared component:
static void Main()
{
SharedDemo quotes = new
SharedDemo(@”C:\ProCSharp\Assemblies\Quotes.txt”);
Console.WriteLine(quotes.GetAssemblyFullName());
Be sure to register the new version of the shared assembly SharedDemo again in the GAC using
gacutil If the referenced version cannot be found, you will get a System.IO.FileLoadException ,
because the binding to the correct assembly failed
With a successful run, you can see the full name of the referenced assembly:
SharedDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7d886a6f7b9f0292
Press any key to continue
This client program can now be used to test different configurations of this shared component
Application Configuration Files
With a configuration file, you can specify that the binding should happen to a different version of a
shared assembly Assume that you create a new version of the shared assembly SharedDemo with
major and minor versions 1.1 Maybe you don ’ t want to rebuild the client but just want the new version
of the assembly to be used with the existing client instead This is useful in cases where either a bug is
fixed with the shared assembly or you just want to get rid of the old version because the new version is
compatible
Trang 7Figure 17 - 19 shows the Global Assembly Cache Viewer, where the versions 1.0.0.0 and 1.0.3300.0 are installed for the SharedDemo assembly
This tool is shipped with Framework SDK and not with the NET runtime, so don ’ t expect this tool to be available to system administrators
Trang 8Select Configured Assemblies in the tree view and the menu Action Add to configure the
dependency of the assembly SharedDemo from the dependency list Select the Binding Policy tab to
define the version that should be used as shown in Figure 17 - 23
Figure 17 - 21
When you select Applications on the left side, and then select Action Add, you can choose a NET
application to configure If the Client.exe application does not show up with the list, click the Other
button and browse to the executable Select the application Client.exe to create an application
configuration file for this application After adding the client application to the NET Configuration
utility, the assembly dependencies can be listed, as shown in Figure 17 - 22
Figure 17 - 22
Trang 9Figure 17 - 23
For the requested version, specify the version referenced in the manifest of the client assembly
newVersion specifies the new version of the shared assembly In Figure 17 - 23 , it is specified that the version 1.0.3300.0 should be used instead of any version in the range of 1.0.0.0 to 1.0.3300.0
Now you can find the application configuration file Client.exe.config in the directory of the
Client.exe application that includes this XML code:
Runtime settings can be configured with the < runtime > element The subelement of < runtime > is
< assemblyBinding > , which in turn has a subelement < dependentAssembly > < dependentAssembly > has a required subelement < assemblyIdentity > You specify the name of the referenced assembly with
< assemblyIdentity > name is the only mandatory attribute for < assemblyIdentity > The optional attributes are publicKeyToken and culture The other subelement of < dependentAssembly > that ’ s needed for version redirection is < bindingRedirect > The old and the new versions of the dependent assembly are specified with this element
When you start the client with this configuration file, you will get the new version of the referenced shared assembly
Trang 10Publisher Policy Files
Using assemblies shared from the GAC allows you to use publisher policies to override versioning
issues Assume that you have an assembly used by some applications What can be done if a critical bug
is found in the shared assembly? You have seen that it is not necessary to rebuild all the applications that
use this shared assembly, because you can use configuration files to redirect to the new version of this
shared assembly Maybe you don ’ t know all the applications that use this shared assembly, but you want
to get the bug fix to all of them In that case, you can create publisher policy files to redirect all
applications to the new version of the shared assembly
Publisher policy files apply only to shared assemblies installed in the GAC
To set up publisher policies, you have to do the following:
Create a publisher policy file
Create a publisher policy assembly
Add the publisher policy assembly to the GAC
Create a Publisher Policy File
A publisher policy file is an XML file that redirects an existing version or version range to a new version
The syntax used here is the same as for application configuration files, so you can use the same file you
created earlier to redirect the old versions 1.0.0.0 through 1.0.3300.0 to the new version 1.0.3300.0
Rename the previously created file to mypolicy.config to use it as a publisher policy file and remove
the element < publisherPolicy > :
Create a Publisher Policy Assembly
To associate the publisher policy file with the shared assembly, it is necessary to create a publisher policy
assembly, and to put it into the GAC The tool that can be used to create such files is the assembly linker
al The option /linkresource adds the publisher policy file to the generated assembly The name of
the generated assembly must start with policy, followed by the major and minor version number of the
assembly that should be redirected, and the file name of the shared assembly In this case the publisher
policy assembly must be named policy.1.0.SharedDemo.dll to redirect the assemblies SharedDemo
with the major version 1 and minor version 0 The key that must be added to this publisher key with the
option /keyfile is the same key that was used to sign the shared assembly SharedDemo to guarantee
that the version redirection is from the same publisher
❑
❑
❑
Trang 11al /linkresource:mypolicy.config /out:policy.1.0.SharedDemo.dll /keyfile: \ \mykey.snk
Add the Publisher Policy Assembly to the GAC
The publisher policy assembly can now be added to the GAC with the utility gacutil :
gacutil -i policy.1.0.SharedDemo.dll
Now remove the application configuration file that was placed in the directory of the client application and start the client application Although the client assembly references 1.0.0.0, you use the new version 1.0.3300.0 of the shared assembly because of the publisher policy
Overriding Publisher Policies
With a publisher policy, the publisher of the shared assembly guarantees that a new version of the assembly is compatible with the old version As you know, from changes of traditional DLLs, such guarantees don ’ t always hold Maybe all except one application is working with the new shared assembly To fix the one application that has a problem with the new release, the publisher policy can be overridden by using an application configuration file
With the NET Framework Configuration tool you can override the publisher policy by deselecting the Enable Publisher Policy check box, as shown in Figure 17 - 24
Trang 12Installing and using multiple versions is not only possible with assemblies but also with the NET
runtime (CLR) The versions 1.0, 1.1, and 2.0 (and later versions) of the CLR can be installed on the same
operating system side by side Visual Studio 2008 targets applications running on CLR 2.0 with NET 2.0,
3.0, and 3.5 With CLR 2.0 the assembly file format changed, so it is not possible to run CLR 2.0
applications with CLR 1.1
If the application is built with CLR 1.1, it is possible to target systems that have only the CLR 1.0 runtime
installed The same can be expected about future minor releases in that they can target CLR 2.0 runtime
versions
An application that was built using CLR 1.0 may run without changes on CLR 1.1 If an operating system
has both versions of the runtime installed, the application will use the version with which it was built
However, if only version 1.1 is installed with the operating system, and the application was built with
version 1.0, it tries to run with the newer version There ’ s a good chance the application runs without
problems The registry key HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework\policy
lists the ranges of the versions that will be used for a specific runtime
If an application was built using NET 1.1, it may run without changes on NET 1.0, in case no classes or
methods are used that are available only with NET 1.1 Here an application configuration file is needed
to make this possible
In an application configuration file, it ’ s not only possible to redirect versions of referenced assemblies;
you can also define the required version of the runtime Different NET runtime versions can be installed
on a single machine You can specify the version that ’ s required for the application in an application
configuration file The element < supportedVersion > marks the runtime versions that are supported by
There is one major point in case you still have NET 1.0 applications that should run on NET 1.1 runtime
versions The element < supportedVersion > was new with NET 1.1 .NET 1.0 used the element
< requiredRuntime > to specify the needed runtime So for NET 1.0 applications, both configurations
must be done as shown here:
< ?xml version=”1.0”? >
< configuration >
< startup >
(continued)
Trang 13You learned the differences between private and shared assemblies and saw how shared assemblies can
be created With private assemblies, you don ’ t have to pay attention to uniqueness and versioning issues because these assemblies are copied and only used by a single application Sharing assemblies requires you to use a key for uniqueness and to define the version You looked at the GAC, which can be used as
an intelligent store for shared assemblies
You can have faster application startups by using the native image generator With this the JIT compiler does not need to run because the native code is created during installation time
You looked at overriding versioning issues to use a version of an assembly different from the one that was used during development; this is done through publisher policies and application configuration files Finally, you learned how probing works with private assemblies
The chapter also discussed loading assemblies dynamically and creating assemblies during runtime
If you want to get more information on this, you should read Chapter 36 about the Add - In model of
.NET 3.5
Trang 15Tracing and Events
Chapter 14 covered errors and exception handling Besides handling exceptional code, it might
be really interesting to get some live information about your running application to find the reason for some issues that application might have during production, or to monitor resources needed to early adapt to higher user loads This is where the namespace System.Diagnostics comes into play
The application doesn ’ t throw exceptions, but sometimes it doesn ’ t behave as expected The application might be running well on most systems but might have a problem on a few On the live system, you change the log behavior by changing a configuration value and get detailed live
information about what ’ s going on in the application This can be done with tracing
If there are problems with applications, the system administrator needs to be informed With the Event Viewer, the system administrator both interactively monitors problems with applications
and gets informed about specific events that happen by adding subscriptions The event - logging
mechanism allows you to write information about the application
To analyze resources needed from applications, monitor applications with specified time intervals, and plan for a different application distribution or extending of system resources, the system administrator uses the performance monitor You can write live data of your application using
With tracing you can see messages from the running application To get some information about a running application, you can start the application in the debugger During debugging, you can walk through the application step by step and set breakpoints at specific lines and when you reach
Trang 16specific conditions The problem with debugging is that a released program can behave differently For
example, while the program is stopping at a breakpoint, other threads of the application are suspended
as well Also, with a release build, the compiler - generated output is optimized and thus different effects
can occur There is a need to have information from a release build as well Trace messages are written
both with debug and release code
A scenario showing how tracing helps is described here After an application is deployed, it runs on
one system without problems, while on another system intermediate problems occur Turning on
verbose tracing on the system with the problems gives you detailed information about what ’ s
happening inside the application The system that is running without problems has tracing configured
just for error messages redirected to the Windows event log system Critical errors are seen by the
system administrator The overhead of tracing is very small, because you configure a trace level only
when needed
The tracing architecture has four major parts:
❑ The source is the originator of the trace information You use the source to send trace messages
❑ The switch defines the level of information to log For example, you can request just error
information or detailed verbose information
❑ Trace listeners define where the trace messages should be written
❑ Listeners can have filters attached The filter defines what trace messages should be written by
the listener This way, you can have different listeners for the same source that write different
levels of information
Figure 18 - 1 shows the major classes for tracing and how they are connected in a Visual Studio class
diagram The TraceSource uses a switch to define what information to log The TraceSource has a
TraceListenerCollection associated where trace messages are forwarded to The collection consists
of TraceListener objects, and every listener has a TraceFilter connected
Figure 18-1
Trang 17Trace Sources
You can write trace messages with the TraceSource class Tracing requires the Trace flag of the compiler settings With a Visual Studio project, the Trace flag is set by default with debug and release builds, but you can change it through the Build properties of the project
The TraceSource class is more difficult to use compared to the Trace class writing trace messages, but it provides more options
To write trace messages, you need to create a new TraceSource instance In the constructor, the name of the trace source is defined The method TraceInformation() writes an information message to the trace output Instead of just writing informational messages, the TraceEvent() method requires an enumeration value of type TraceEventType to define the type of the trace message TraceEventType.Error specifies the message as an error message You can define it with a trace switch to see only error messages The second argument of the TraceEvent() method requires an identifier The ID can be used within the application itself For example, you can use id 1 for entering a method and id 2 for exiting a method The method TraceEvent() is overloaded, so the TraceEventType and the ID are the only required parameters Using the third parameter of an overloaded method, you can pass the message written to the trace TraceEvent() also supports passing a format string with any number of parameters in the same way as Console.WriteLine() TraceInformation() does nothing more than invoke TraceEvent() with an identifier of 0 TraceInformation() is just a simplified version of
TraceEvent() With the TraceData() method, you can pass any object, for example an exception instance, instead of a message To make sure that data is written by the listeners and does not stay in memory, you need to do a Flush() If the source is no longer needed, you can invoke the Close() method that closes all listeners associated with the trace source Close() does a Flush() as well
TraceSource source1 = new TraceSource(“Wrox.ProCSharp.Tracing”);
source1.TraceInformation(“Info message”);
source1.TraceEvent(TraceEventType.Error, 3, “Error message”);
source1.TraceData(TraceEventType.Information, 2, new int[] { 1, 2, 3 });
source1.Flush();
source1.Close();
You can use different trace sources within your application It makes sense to define different sources for different libraries, so that you can turn on different trace levels for different parts of your application To use a trace source you need to know its name A commonly used name for the trace source is the same name as the namespace
The TraceEventType enumeration that is passed as an argument to the TraceEvent() method defines the following levels to specify the severity of the problem: Verbose , Information , Warning ,
Error , and Critical Critical defines a fatal error or application crash; Error defines a recoverable error Trace messages at the Verbose level give you detailed debugging information TraceEventType also defines action levels Start , Stop , Suspend , and Resume These levels define timely events inside a logical operation
The code, as it is written now, does not display any trace message because the switch associated with the trace source is turned off
Trang 18Trace Switches
To enable or disable trace messages, you can configure a trace switch Trace switches are classes that are
derived from the abstract base class Switch Derived classes are BooleanSwitch , TraceSwitch , and
SourceSwitch The class BooleanSwitch can be turned on and off, and the other two classes provide a
range level that is defined by the TraceLevel enumeration To configure trace switches, you must know
the values associated with the TraceLevel enumeration TraceLevel defines the values Off , Error ,
Warning , Info , and Verbose
You can associate a trace switch programmatically by setting the Switch property of the TraceSource
Here the switch associated is of type SourceSwitch , has the name MySwitch , and has the level
Verbose :
TraceSource source1 = new TraceSource(“Wrox.ProCSharp.Tracing”);
source1.Switch = new SourceSwitch(“MySwitch”, “Verbose”);
Setting the level to Verbose means that all trace messages should be written If you set the value to
Error , only error messages should show up Setting the value to Information means that error,
warning, and info messages are shown Writing the trace messages once more, you can see the messages
while running the debugger in the Output window
Usually, you would want to change the switch level not by recompiling the application, but instead by
changing the configuration The trace source can be configured in the application configuration file
Tracing is configured within the < system.diagnostics > element The trace source is defined with the
< source > element as a child element of < sources > The name of the source in the configuration file
must exactly match the name of the source in the program code Here, the trace source has a switch of
type System.Diagnostics.SourceSwitch associated with the name MySourceSwitch The switch
itself is defined within the < switches > section, and the level of the switch is set to verbose
Now, you can change the trace level just by changing the configuration file without the need to
recompile the code After the configuration file is changed, you must restart the application
Currently, trace messages are written to just the Output window of Visual Studio while you are running
it in a debug session Adding trace listeners changes this
Trace Listeners
By default, trace information is written to the Output window of the Visual Studio debugger Just by
changing the application configuration, you can redirect the trace output to different locations
Where tracing should be written to is defined by trace listeners A trace listener is derived from the
abstract base class TraceListener
Trace listeners defined by the NET Framework are described in the following table
Trang 19Trace Listener Description
DefaultTraceListener A default trace listener is automatically added to the listeners
collection of the Trace class Default output goes to the attached debugger In Visual Studio, this is shown in the Output window during a debugging session
EventLogTraceListener The EventLogTraceListener writes trace information to the
event log With the constructor of the
EventLogTraceListener , you can specify an event log source
or an object of type EventLog Event logging is described later
in this chapter
TextWriterTraceListener With the TextWriterTraceListener trace, output can be
writ-ten to a file, a TextWriter , or a Stream See Chapter 25 , “ Manipulating Files and the Registry, ” for file manipulation information
Text WriterTraceListener is the base class of
ConsoleTraceListener , DelimitedListTraceListener , and XmlWriterTraceListener
ConsoleTraceListener ConsoleTraceListener writes trace messages to the console
DelimitedListTraceListener DelimitedListTraceListener writes trace messages to a
delimited file With trace output options, you can define a lot of separate tracing information such as process ID, time, and the like, which can be read more easily with a delimited file
XmlWriterTraceListener Instead of using a delimited file, you can redirect the trace
infor-mation to an XML file with the XmlWriterTraceListener
IisTraceListener The IisTraceListener was added in NET 3.0
WebPageTraceListener ASP.NET has another tracing option to get ASP.NET trace
infor-mation about Web pages in a dynamically created output file
trace axd If you configure the WebPageTraceListener , then
System.Diagnostics trace information goes into trace.axd
to your phone in your spare time And with verbose tracing this can become really expensive
You can configure a trace listener programmatically by creating a listener object and assigning it to the
Listeners property of the TraceSource class However, usually it is more interesting to just change a configuration to define a different listener
You can configure listeners as child elements of the < source > element With the listener, you define the type of the listener class and use initializeData to specify where the output of the listener should
go The configuration here defines the XmlWriterTraceListener to write to the file demotrace.xml and the DelimitedListTraceListener to write to the file demotrace.txt :
Trang 20You might get a warning from the XML schema regarding the delimiter attribute declaration You
can ignore it
With the listener, you can also specify what additional information should be written to the trace
log This information is defined with the traceOutputOptions XML attribute and is defined
by the TraceOptions enumeration The enumeration defines Callstack , DateTime ,
LogicalOperationStack , ProcessId , ThreadId , and None The information needed can
be added with comma separation to the traceOutputOptions XML attribute, as shown with the
delimited trace listener
The delimited file output from the DelimitedListTraceListener , including the process ID and date/
time, is shown here:
“Wrox.ProCSharp.Tracing”:Information:0:”Info message”::4188:””::
“2007-01-23T12:38:31.3750000Z”::
“Wrox.ProCSharp.Tracing”:Error:3:”Error message”::4188:””::
“2007-01-23T12:38:31.3810000Z”::
The XML output from the XmlWriterTraceListener always contains the name of the computer, the
process ID, the thread ID, the message, the time created, the source, and the activity ID Other fields,
such as the call stack, logical operation stack, and timestamp, depend on the trace output options
You can use the XmlDocument and XPathNavigator classes to analyze the content from the XML
file These classes are covered in Chapter 28 , “ Manipulating XML ”
If a listener should be used by multiple trace sources, you can add the listener configuration to the
element < sharedListeners > , which is independent of the trace source The name of the listener that is
configured with a shared listener must be referenced from the listeners of the trace source:
< ?xml version=”1.0” encoding=”utf-8” ? >
< configuration >
< system.diagnostics >
< sources >
Trang 21A filter is a class that is derived from the abstract base class TraceFilter NET 3.0 offers two filter implementations: SourceFilter and EventTypeFilter With the source filter, you can specify that trace messages are to be written only from specific sources The event type filter is an extension to the switch functionality With a switch, it is possible to define, according to the trace severity level, if the event source should forward the trace message to the listeners If the trace message is forwarded, the listener now can use the filter to decide if the message should be written
The changed configuration now defines that the delimited listener should write trace messages only if the severity level is of type warning or higher, because of the defined EventTypeFilter The XML listener specifies a SourceFilter and accepts trace messages only from the source
Wrox.ProCSharp.Tracing In case you have a large number of sources defined to write trace messages to the same listener, you can change the configuration for the listener to concentrate on trace messages from a specific source
Trang 22The tracing architecture can be extended Just as you can write a custom listener derived from the base
class TraceListener , you can also create a custom filter derived from TraceFilter With that
capability, you can create a filter that specifies to write trace messages, for example, depending on the
time, depending on an exception that occurred lately, or depending on the weather
Asserts
Another feature that belongs to tracing are asserts Asserts are critical problems within the program path
With asserts, a message is displayed with the error, and you can abort or continue the application
Asserts are very helpful when you write a library that is used by another developer
With the Foo() method, Trace.Assert() examines parameter o to see if it is not null If the condition
is false , the error message as shown in Figure 18 - 2 is issued If the condition is true , the program
(continued)
Figure 18-2
Trang 23continues The Bar() method includes a Trace.Assert() example where it is verified that the parameter is larger than 10 and smaller than 20 If the condition is false , an error message is shown again
static void Foo(object o) {
Trace.Assert(o != null, “Expecting an object”);
Console.WriteLine(o);
} static void Bar(int x) {
Trace.Assert(x > 10 & & x < 20, “x should be between 10 and 20”);
Console.WriteLine(x);
} static void Main() {
Trace messages can be written to the event log if you configure the EventLogTraceListener class The
EventLogTraceListener has an EventLog object associated with it to write the event log entry You can also use the EventLog class directly to write and read event logs
In this section, you explore the following:
❑ Event - logging architecture
❑ Classes for event logging from the System.Diagnostics namespace
❑ Adding event logging to services and to other application types
❑ Creating an event log listener with the EnableRaisingEvents property of the EventLog class Figure 18 - 3 shows an example of a log entry from a modem
Trang 24For custom event logging, you can use classes from the System.Diagnostics namespace
Event - Logging Architecture
The event log information is stored in several log files The most important ones are application, security,
and system Looking at the registry configuration of the event log service, you will notice several entries
under HKEY _ LOCAL _ MACHINE\System\CurrentControlSet\Services\Eventlog with configurations
pointing to the specific files The system log file is used from the system and device drivers Applications
and services write to the application log The security log is a read - only log for applications The
audit-ing feature of the operataudit-ing system uses the security log Every application can also create a custom
category and log file to write event log entries there For example, this is done by Windows OneCare and
Media Center
You can read these events by using the administrative tool Event Viewer The Event Viewer can be
started directly from the Server Explorer of Visual Studio by right - clicking the Event Logs item and
selecting the Launch Event Viewer entry from the context menu The Event Viewer is shown in
Figure 18 - 4
In the event log, you can see this information:
❑ Type — The type can be Information, Warning, or Error Information is an infrequent successful
operation; Warning is a problem that is not immediately significant; and Error is a major
problem Additional types are FailureAudit and SuccessAudit, but these types are used only for
the security log
❑ Date — Date and Time show the time when the event occurred
Figure 18-3
Trang 25❑ Source — The Source is the name of the software that logs the event The source for the
applica-tion log is configured in:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog\Application\
[ApplicationName]
Below this key, the value EventMessageFile is configured to point to a resource DLL that holds error messages
❑ Category — A Category can be defined so that event logs can be filtered when using the Event
Viewer Categories can be defined by an event source
❑ Event identifier — The Event identifier specifies a particular event message
Event - Logging Classes
The System.Diagnostics namespace has some classes for event logging, which are shown in the following table
Class Description
EventLog With the EventLog class, you can read and write entries in the event
log, and establish applications as event sources
EventLogEntry The EventLogEntry class represents a single entry in the event log
With the EventLogEntryCollection , you can iterate through
EventLogEntry items
Figure 18-4
Trang 26Class Description
EventLogInstaller The EventLogInstaller class is the installer for an EventLog
component EventLogInstaller calls EventLog.CreateEventSource() to create an event source
EventLogTraceListener With the help of the EventLogTraceListener , traces can be written
to the event log This class implements the abstract class
Entries With the Entries property, you can read event logs Entries
returns an EventLogEntryCollection that contains
EventLogEntry objects holding information about the events
There is no need to invoke a Read() method The collection is filled as soon as you access this property
Log property
LogDisplayName LogDisplayName is a read - only property that returns the display
name of the log
MachineName With the MachineName , you can specify the system on which to
read or write log entries
Source The Source property specifies the source of the event entries
to write
CreateEventSource() The CreateEventSource() creates a new event source and a
new log file, if a new log file is specified with this method
DeleteEventSource() To get rid of an event source, you can invoke
DeleteEventSource()
SourceExists() Before creating an event source, you can verify if the source
already exists by using this element
WriteEntry()WriteEvent() Write event log entries with either the WriteEntry() or
WriteEvent() method WriteEntry() is simpler, because you just need to pass a string WriteEvent() is more flexible, because you can use message files that are independent of the application and that support localization
Trang 27Creating an Event Source
Before writing events, you must create an event source You can use either the CreateEventSource() method of the EventLog class or the class EventLogInstaller Because you need administrative privileges when creating an event source, an installation program would be best for defining the new source
Chapter 16 , “ Deployment, ” explains how to create installation programs
The following sample verifies that an event log source named EventLogDemoApp already exists If it doesn ’ t exist, an object of type EventSourceCreationData is instantiated that defines the source name
EventLogDemoApp and the log name ProCSharpLog Here, all events of this source are written to the
ProCSharpLog event log The default is the application log
if (!EventLog.SourceExists(“EventLogDemoApp”)) {
EventSourceCreationData eventSourceData = new EventSourceCreationData(“EventlogDemoApp”, “ProCSharpLog”);
EventLog.CreateEventSource(eventSourceData);
}
The name of the event source is an identifier of the application that writes the events For the system administrator reading the log, the information helps in identifying the event log entries to map them to application categories Examples of names for event log sources are LoadPerf for the performance monitor, MSSQLSERVER for Microsoft SQL Server, MsiInstaller for the Windows Installer, Winlogon ,
Tcpip , Time - Service , and so on
Setting the name Application for the event log writes event log entries to the application log You can also create your own log by specifying a different application log name Log files are located in the directory < windows > \System32\WinEvt\Logs
With the EventSourceCreationData , you can also specify several more characteristics for the event log, as shown in the following table
EventSourceCreationData Description
Source The property Source gets or sets the name of the event source
LogName LogName defines the log where event log entries are written The
default is the application log
MachineName With MachineName , you can define the system to read or write log
entries
CategoryResourceFile With the CategoryResourceFile property, you can define a
resource file for categories Categories can be used for an easier ing of event log entries within a single source
CategoryCount The CategoryCount property defines the number of categories in the
category resource file
Trang 28EventSourceCreationData Description
MessageResourceFile Instead of specifying that the message should be written to the event
log in the program that writes the events, messages can be defined in
a resource file that is assigned to the MessageResourceFile erty Messages from the resource file are localizable
ParameterResourceFile Messages in a resource file can have parameters The parameters can
be replaced by strings defined in a resource file that is assigned to the
ParameterResourceFile property
Writing Event Logs
For writing event log entries, you can use the WriteEntry() or WriteEvent() methods of the
EventLog class
The EventLog class has both a static and an instance method WriteEntry() The static method
WriteEntry() requires a parameter of the source The source can also be set with the constructor of
the EventLog class Here in the constructor, the log name, the local machine, and the event source name
are defined Next, three event log entries are written with the message as the first parameter of the
WriteEntry() method WriteEntry() is overloaded The second parameter you can assign is an
enumeration of type EventLogEntryType With EventLogEntryType , you can define the severity of
the event log entry Possible values are Information , Warning , and Error , and for auditing
SuccessAudit and FailureAudit Depending on the type, different icons are shown in the Event
Viewer With the third parameter, you can specify an application - specific event ID that can be used by
the application itself In addition to that, you can also pass application - specific binary data and
Instead of defining the messages for the event log in the C# code and passing it to the WriteEntry()
method, you can create a message resource file , define messages in the resource file, and pass message
identifiers to the WriteEvent() method Resource files also support localization
Message resource files are native resource files that have nothing in common with NET resource files
.NET resource files are covered in Chapter 21 , “ Localization ”
A message file is a text file with the mc file extension The syntax that this file uses to define messages is
very strict The sample file EventLogMessages.mc contains four categories followed by event messages
Every message has an ID that can be used by the application writing event entries Parameters that can
be passed from the application are defined with % syntax in the message text
Trang 29For the exact syntax of message files, check the MSDN documentation for Message Text Files
Installation
MessageId=0x2Severity=SuccessSymbolicName=DATA_CATEGORYLanguage=English
Database Query
MessageId=0x3Severity=SuccessSymbolicName=UPDATE_CATEGORYLanguage=English
Data Update
MessageId=0x4Severity=SuccessSymbolicName=NETWORK_CATEGORYLanguage=English
Network Communication
; // Event messages
-; // *********************************
MessageId = 1000Severity = SuccessFacility = ApplicationSymbolicName = MSG_CONNECT_1000Language=English
Connection successful
MessageId = 1001Severity = ErrorFacility = ApplicationSymbolicName = MSG_CONNECT_FAILED_1001Language=English
Could not connect to server %1
Trang 30
; // Event message parameters
-; // Language independent insertion strings
Use the Messages Compiler, mc.exe , to create a binary message file mc – s EventLogDemoMessages
.mc compiles the source file containing the messages to a messages file with the bin extension and the
file Messages.rc , which contains a reference to the binary message file:
mc -s EventLogDemoMessages.mc
Next, you must use the Resource Compiler, rc.exe rc EventLogDemoMessages.rc creates the
resource file EventLogDemoMessages.RES :
rc EventLogDemoMessages.rc
With the linker, you can bind the binary message file EventLogDemoMessages.RES to a native DLL:
link /DLL /SUBSYSTEM:WINDOWS /NOENTRY /MACHINE:x86 EventLogDemoMessages.RES
(continued)
Trang 31Now, you can register an event source that defines the resource files as shown in the following code
First, a check is done if the event source named EventLogDemoApp exists If the event log must be created because it does not exist, the next check verifies if the resource file is available Some samples in the MSDN documentation demonstrate writing the message file to the < windows > \system32 directory, but you shouldn ’ t do that Copy the message DLL to a program - specific directory that you can get with the SpecialFolder enumeration value ProgramFiles If you need to share the messages file among multiple applications, you can put it into Environment.SpecialFolder.CommonProgramFiles If the file exists, a new object of type EventSourceCreationData is instantiated In the constructor, the name
of the source and the name of the log are defined You use the properties CategoryResourceFile ,
MessageResourceFile , and ParameterResourceFile to define a reference to the resource file After the event source is created, you can find the information on the resource files in the registry with the event source The method CreateEventSource registers the new event source and log file Finally, the method RegisterDisplayName() from the EventLog class specifies the name of the log as it is displayed in the Event Viewer The ID 5001 is taken from the message file
If you want to delete a previously created event source, you can do so with EventLog.DeleteEventSource(sourceName); To delete a log, you can invoke EventLog.Delete(logName);
string logName = “ProCSharpLog”;
string sourceName = “EventLogDemoApp”;
string resourceFile = Environment.GetFolderPath(
Environment.SpecialFolder.ProgramFiles) + @”\procsharp\EventLogDemoMessages.dll”;
if (!EventLog.SourceExists(sourceName)) {
if (!File.Exists(resourceFile)) {
Console.WriteLine(“Message resource file does not exist”);
return;
} EventSourceCreationData eventSource = new EventSourceCreationData(sourceName, logName);
eventSource.CategoryResourceFile = resourceFile;
eventSource.CategoryCount = 4;
eventSource.MessageResourceFile = resourceFile;
eventSource.ParameterResourceFile = resourceFile;
EventLog.CreateEventSource(eventSource);
} else { logName = EventLog.LogNameFromSourceName(sourceName, “.”);
} EventLog evLog = new EventLog(logName, “.”, sourceName);
evLog.RegisterDisplayName(resourceFile, 5001);
Now, you can use the WriteEvent() method instead of WriteEntry() to write the event log entry
WriteEvent() requires an object of type EventInstance as parameter With the EventInstance , you can assign the message ID, the category, and the severity of type EventLogEntryType In addition to the
EventInstance parameter, WriteEvent() accepts parameters for messages that have parameters and binary data as byte array
Trang 32EventLog log = new EventLog(logName, “.”, sourceName);
EventInstance info1 = new EventInstance(1000, 4,
For the message identifiers, it is useful to define a class with const values that provide a more
meaning-ful name for the identifiers in the application
You can read the event log entries with the Event Viewer
Event Log Listener
Instead of using the Event Viewer to read event log entries, you can create a custom event log reader that
listens for events of specified types as needed You can create a reader where important messages pop up
to the screen, or send SMS to a system administrator
Next, you write an application that receives an event when a service encounters a problem Create a
simple Windows application that monitors the events of your Quote service This Windows application
consists of a list box and an Exit button only, as shown in Figure 18 - 5
Figure 18-5
Add an EventLog component to the design view by dragging and dropping it from the toolbox Set the
Log property to Application You can set the Source property to a specific source to receive event log
entries from only this source, for example the source EventLogDemoApp for receiving the event logs
from the application created previously If you leave the Source property empty, you will receive
Trang 33events from every source You also need to change the property EnableRaisingEvents The default value is false ; setting it to true means that an event is generated each time this event occurs, and you can add an event handler for the EntryWritten event of the EventLog class Add a handler with the name OnEntryWritten() to this event
The OnEntryWritten() handler receives an EntryWrittenEventArgs object as argument, from which you can get the complete information about an event With the Entry property, an EventLogEntry object with information about the time, event source, type, category, and so on is returned:
protected void OnEntryWritten (object sender, System.Diagnostics.EntryWrittenEventArgs e) {
StringBuilder sb = new StringBuilder();
sb.AppendFormat(“{0} {1} {2}”, e.Entry.TimeGenerated.ToShortTimeString(), e.Entry.Source,
Microsoft Windows has many performance objects, such as System , Memory , Objects , Process ,
Processor , Thread , Cache , and so on Each of these objects has many counts to monitor For example, with the Process object, the user time, handle count, page faults, thread count, and so on can be monitored for all processes or for specific process instances Some applications, such as SQL Server, also add application - specific objects
For the quote service sample application, it might be interesting to get information about the number of client requests, the size of the data sent over the wire, and so on
Figure 18-6
Trang 34Performance - Monitoring Classes
The System.Diagnostics namespace provides these classes for performance monitoring:
❑ PerformanceCounter can be used both to monitor counts and to write counts New
performance categories can also be created with this class
❑ PerformanceCounterCategory enables you to step through all existing categories as well as
create new ones You can programmatically get all the counters in a category
❑ PerformanceCounterInstaller is used for the installation of performance counters Its use is
similar to that of the EventLogInstaller discussed previously
Performance Counter Builder
The sample application is a simple Windows application with just one button so that you can see how
to write performance counts In a similar way, you can add performance counters to a Windows
Service (see Chapter 23 , “ Windows Services ” ), to a network application (see Chapter 41 , “ Accessing
the Internet ” ), or to any other application from which you would like to receive live counts
Using Visual Studio, you can create a new performance counter category by selecting the performance
counters in the Server Explorer and by selecting the menu entry Create New Category on the context
menu This launches the Performance Counter Builder (see Figure 18 - 7 )
Figure 18-7
Trang 35Set the name of the performance counter category to Wrox Performance Counters The following table shows all performance counters of the quote service
# of Button clicks Total # of button clicks NumberOfItems32 # of Button clicks/sec # of button clicks in one second RateOfCountsPerSecond32 # of Mouse move events Total # of mouse move events NumberOfItems32
# of Mouse move events/sec # of mouse move events in one
Adding PerformanceCounter Components
Now you can add PerformanceCounter components from the toolbox Instead of using the components from the toolbox category Components, you can directly drag and drop the previously created performance counters from the Server Explorer to the design view This way, the instances are configured automatically; the CategoryName property is set to “ Wrox Performance Counters ” for all objects, and the CounterName property is set to one of the values available in the selected category
Because with this application the performance counts will not be read but written, you must set the
ReadOnly property to false Also, set the MachineName property to so that the application writes performance counts locally
Here is a part of the code generated into InitalizeComponent() by adding the PerformanceCounter components to the Designer and by setting the properties as indicated previously:
private void InitializeComponent() {
//
//
// performanceCounterButtonClicks //
this.performanceCounterButtonClicks.CategoryName = “Wrox Performance Counts”;
this.performanceCounterButtonClicks.CounterName = “# of Button Clicks”;
this.performanceCounterButtonClicks.ReadOnly = false;
//
// performanceCounterButtonClicksPerSec //
this.performanceCounterButtonClicksPerSec.CategoryName = “Wrox Performance Counts”;
this.performanceCounterButtonClicksPerSec.CounterName = “# of Button Clicks / sec”;
(continued)
Trang 36For the calculation of the performance values, you need to add the fields clickCountPerSec and
mouseMoveCountPerSec to the class Form1 :
public partial class Form1 : Form
{
// Performance monitoring counter values
private int clickCountPerSec = 0;
private int mouseMoveCountPerSec = 0;
Add an event handler to the Click event of the button and an event handler to the MouseMove event to
the form, and add the following code to the handlers:
private void button1_Click(object sender, EventArgs e)
The Increment() method of the PerformanceCounter object increments the counter by one If you
need to increment the counter by more than one, for example to add information about a byte count
sent or received, you can use the IncrementBy() method For the performance counts that show
the value in seconds, just the two variables, clickCountPerSec and mouseMovePerSec , are
incremented
To show updated values every second, add a Timer component Set the OnTimer() method to the
Elapsed event of this component The OnTimer() method is called once per second if you set
the Interval property to 1000 In the implementation of this method, set the performance counts by
using the RawValue property of the PerformanceCounter class:
(continued)
Trang 37protected void OnTimer (object sender, System.Timers.ElapsedEventArgs e) {
performanceCounterButtonClicksPerSec.RawValue = clickCountPerSec;
clickCountPerSec = 0;
performanceCounterMouseMoveEventsPerSec.RawValue = mouseMoveCountPerSec;
mouseMoveCountPerSec = 0;
}
The timer must be started:
public Form1() {
InitializeComponent();
this.timer1.Start();
Figure 18-8
Trang 38Summar y
In this chapter, you have seen tracing and logging facilities that can help you find intermediate problems
in your applications You should plan early, building these features into your applications This will help
you avoid many troubleshooting problems later
With tracing, you can write debugging messages to an application that can also be used for the final
product delivered In case there are problems, you can turn tracing on by changing configuration values,
and find the issues
Event logging provides information to the system administrator to help find some of the critical issues
with the application Performance monitoring helps in analyzing the load from applications and in
planning in advance for resources that might be required in the future
In the next chapter you learn all about writing multithreaded applications
Figure 18-9
After you have added the counters to the performance monitor, you can see the actual values of the
service over time (see Figure 18 - 9 ) Using this performance tool, you can also create log files to analyze
the performance at a later time
Trang 39Threading and Synchronization
There are several reasons for using threading Suppose that you are making a network call from an application that might take some time You don ’ t want to stall the user interface and just let the user wait until the response is returned from the server The user could do some other actions in the meantime or even cancel the request that was sent to the server Using threads can help
For all activities that require a wait — for example, because of a file, database, or network access —
a new thread can be started to fulfill other tasks at the same time Even if you have only processing - intensive tasks to do, threading can help Multiple threads of a single process can run
on different CPUs, or, nowadays, on different cores of a multiple - core CPU, at the same time
You must be aware of some issues when running multiple threads, however Because they can run during the same time, you can easily get into problems if the threads access the same data You must implement synchronization mechanisms
This chapter provides the foundation you will need when programming applications with multiple threads, including:
An overview of threading Lightweight threading using delegates Thread class
Thread pools Threading issues Synchronization techniques Timers
COM apartments Event - based asynchronous pattern
Trang 40Over view
A thread is an independent stream of instructions in a program All your C# programs up to this point
have one entry point — the Main() method Execution starts with the first statement in the Main()
method and continues until that method returns
This program structure is all very well for programs, in which there is one identifiable sequence of tasks,
but often a program needs to do more than one thing at the same time Threads are important both for
client - side and for server - side applications While you type C# code in the Visual Studio editor, the
Dynamic Help window immediately shows the topics that fit to the code you type A background thread
is searching through help The same thing is done by the spell checker in Microsoft Word One thread is
waiting for input from the user, while the other does some background research A third thread can store
the written data in an interim file, while another one downloads some additional data from the Internet
In an application that is running on the server, one thread, the listener thread, waits for a request from a
client As soon as the request comes in, the request is forwarded to a separate worker thread, which
continues the communication with the client The listener thread immediately comes back to get the next
request from the next client
With the Windows Task Manager, you can turn on the column Threads from the menu View Select
Columns and see the processes and the number of threads for every process Only cmd.exe is running
inside a single thread; all the other applications shown in Figure 19 - 1 use multiple threads You can see
one instance of Internet Explorer running 51 threads
Figure 19-1
A process contains resources, such as Window handles, handles to the file system, or other kernel objects
Every process has virtual memory allocated A process contains at least one thread The operating
system schedules threads A thread has a priority, a program counter for the program location where it
is actually processing, and a stack to store its local variables Every thread has its own stack, but the
memory for the program code and the heap are shared among all threads of a single process This makes
communication among threads of one process fast — the same virtual memory is addressed by all
threads of a process However, this also makes things difficult because multiple threads can change the
same memory location