
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
384 C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S
Figure 11-28. Behold! The GAC’s internal copy of CarLibrary.dll.
When you install a strongly named assembly into the GAC, the operating system responds by extending the directory structure beneath the Assembly subdirectory. Using this approach, the CLR is able to manipulate multiple versions of a specific assembly while avoiding the expected name clashes resulting from identically named *.dlls.
Understanding Publisher Policy Assemblies
The next configuration issue you’ll examine is the role of publisher policy assemblies. As you’ve just seen, *.config files can be constructed to bind to a specific version of a shared assembly, thereby bypassing the version recorded in the client manifest. While this is all well and good, imagine you’re an administrator who now needs to reconfigure all client applications on a given machine to rebind to version 2.0.0.0 of the CarLibrary.dll assembly. Given the strict naming convention of a configuration file, you would need to duplicate the same XML content in numerous locations (assuming you are, in fact, aware of the locations of the executables using CarLibrary!). Clearly this would be a maintenance nightmare.
Publisher policy allows the publisher of a given assembly (you, your department, your company, or what have you) to ship a binary version of a *.config file that is installed into the GAC along with the newest version of the associated assembly. The benefit of this approach is that client application directories do not need to contain specific *.config files. Rather, the CLR will read the current manifest and attempt to find the requested version in the GAC. However, if the CLR finds a publisher policy assembly, it will read the embedded XML data and perform the requested redirection at the level of the GAC.
Publisher policy assemblies are created at the command line using a .NET utility named al.exe (the assembly linker). While this tool provides a large number of options, building a publisher policy assembly requires you only to pass in the following input parameters:
•The location of the *.config or *.xml file containing the redirecting instructions
•The name of the resulting publisher policy assembly
•The location of the *.snk file used to sign the publisher policy assembly
•The version numbers to assign the publisher policy assembly being constructed
If you wish to build a publisher policy assembly that controls CarLibrary.dll, the command set is as follows:
al /link: CarLibraryPolicy.xml /out:policy.1.0.CarLibrary.dll /keyf:C:\MyKey\myKey.snk /v:1.0.0.0

C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S |
385 |
Here, the XML content is contained within a file named CarLibraryPolicy.xml. The name of the output file (which must be in the format policy.<major>.<minor>.assemblyToConfigure) is specified using the obvious /out flag. In addition, note that the name of the file containing the public/private key pair will also need to be supplied via the /keyf option. (Remember, publisher policy files are shared, and therefore must have a strong name!)
Once the al.exe tool has executed, the result is a new assembly that can be placed into the GAC to force all clients to bind to version 2.0.0.0 of CarLibrary.dll, without the use of a specific client application configuration file.
Disabling Publisher Policy
Now, assume you (as a system administrator) have deployed a publisher policy assembly (and the latest version of the related assembly) to a client machine. As luck would have it, nine of the ten affected applications rebind to version 2.0.0.0 without error. However, the remaining client application (for whatever reason) blows up when accessing CarLibrary.dll 2.0.0.0 (as we all know, it is next to impossible to build backward-compatible software that works 100 percent of the time).
In such a case, it is possible to build a configuration file for a specific troubled client that instructs the CLR to ignore the presence of any publisher policy files installed in the GAC. The remaining client applications that are happy to consume the newest .NET assembly will simply be redirected via the installed publisher policy assembly. To disable publisher policy on a client-by-client basis, author a (properly named) *.config file that makes use of the <publisherPolicy> element and set the apply attribute to no. When you do so, the CLR will load the version of the assembly originally listed in the client’s manifest.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<publisherPolicy apply="no" />
</assemblyBinding>
</runtime>
</configuration>
Understanding the <codeBase> Element
Application configuration files can also specify code bases. The <codeBase> element can be used to instruct the CLR to probe for dependent assemblies located at arbitrary locations (such as network share points, or simply a local directory outside a client’s application directory).
■Note If the value assigned to a <codeBase> element is located on a remote machine, the assembly will be downloaded on demand to a specific directory in the GAC termed the download cache. You can view the content of your machine’s download cache by supplying the /ldl option to gacutil.exe.
Given what you have learned about deploying assemblies to the GAC, it should make sense that assemblies loaded from a <codeBase> element will need to be assigned a strong name (after all, how else could the CLR install remote assemblies to the GAC?).
■Note Technically speaking, the <codeBase> element can be used to probe for assemblies that do not have
a strong name. However, the assembly’s location must be relative to the client’s application directory (and thus is little more than an alternative to the <privatePath> element).

386 C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S
Create a console application named CodeBaseClient, set a reference to CarLibrary.dll version 2.0.0.0, and update the initial file as so:
using CarLibrary;
namespace CodeBaseClient
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with CodeBases *****");
SportsCar c = new SportsCar(); Console.WriteLine("Sports car has been allocated."); Console.ReadLine();
}
}
}
Given that CarLibrary.dll has been deployed to the GAC, you are able to run the program as is. However, to illustrate the use of the <codeBase> element, create a new folder under your C drive (perhaps C:\MyAsms) and place a copy of CarLibrary.dll version 2.0.0.0 into this directory.
Now, add an App.config file to the CodeBaseClient project (as explained earlier in this chapter) and author the following XML content (remember that your .publickeytoken value will differ; consult your GAC as required):
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly>
<assemblyIdentity name="SharedAssembly" publicKeyToken="219ef380c9348a38" />
<codeBase version="2.0.0.0" href="file:///C:\MyAsms\CarLibrary.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
As you can see, the <codeBase> element is nested within the <assemblyIdentity> element, which makes use of the name and publicKeyToken attributes to specify the friendly name as associated publicKeyToken values. The <codeBase> element itself specifies the version and location (via the href property) of the assembly to load. If you were to delete version 2.0.0.0 of CarLibrary.dll from the GAC, this client would still run successfully, as the CLR is able to resolve locate the external assembly under C:\MyAsms.
However, if you were to delete the MyAsms directory from your machine, the client would now fail. Clearly the <codeBase> elements (if present) take precedence over the investigation of the GAC.
■Note If you place assemblies at random locations on your development machine, you are in effect re-creating the system registry (and the related DLL hell), given that if you move or rename the folder containing your binaries, the current bind will fail. Given this point, use <codeBase> with caution.
The <codeBase> element can also be helpful when referencing assemblies located on a remote networked machine. Assume you have permission to access a folder located at http://www. IntertechTraining.com. To download the remote *.dll to the GAC’s download cache on your location machine, you could update the <codeBase> element as so:

C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S |
387 |
<codeBase version="2.0.0.0" href="http://www.IntertechTraining.com/Assemblies/CarLibrary.dll" />
■Source Code The CodeBaseClient application can be found under the Chapter 11 subdirectory.
The System.Configuration Namespace
Currently, all of the *.config files shown in this chapter have made use of well-known XML elements that are read by the CLR to resolve the location of external assemblies. In addition to these recognized elements, it is perfectly permissible for a client configuration file to contain application-specific data that has nothing to do with binding heuristics. Given this, it should come as no surprise that the .NET Framework provides a namespace that allows you to programmatically read the data within a client configuration file.
The System.Configuration namespace provides a small set of types you may use to read custom data from a client’s *.config file. These custom settings must be contained within the scope of an <appSettings> element. The <appSettings> element contains any number of <add> elements that define a key/value pair to be obtained programmatically.
For example, assume you have a *.config file for a console application named AppConfigReaderApp that defines a database connection string and a point of data named timesToSayHello:
<configuration>
<appSettings>
<add key="appConStr" value="server=localhost;uid='sa';pwd='';database=Cars" />
<add key="timesToSayHello" value="8" />
</appSettings>
</configuration>
Reading these values for use by the client application is as simple as calling the instance-level
GetValue() method of the System.Configuration.AppSettingsReader type. As shown in the following code, the first parameter to GetValue() is the name of the key in the *.config file, whereas the second parameter is the underlying type of the key (obtained via the C# typeof operator):
class Program
{
static void Main(string[] args)
{
//Create a reader and get the connection string value.
AppSettingsReader ar = new AppSettingsReader(); Console.WriteLine(ar.GetValue("appConStr", typeof(string)));
//Now get the number of times to say hello, and then do it!
int numbOfTimes = (int)ar.GetValue("timesToSayHello", typeof(int)); for(int i = 0; i < numbOfTimes; i++)
Console.WriteLine("Yo!");
Console.ReadLine();
}
}
The AppSettingsReader class type does not provide a way to write application-specific data to a *.config file. While this may seem like a limitation at first encounter, it actually makes good sense. The whole idea of a *.config file is that it contains read-only data that is consulted by the CLR (or possibly the AppSettingsReader type) after an application has already been deployed to a target machine.

388C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S
■Note During our examination of ADO.NET (Chapter 22) you will learn about the new <connectionStrings> configuration element and new types within the System.Configuration namespace. These .NET 2.0–specific items provide a standard manner to handle connection string data.
■Source Code The AppConfigReaderApp application can be found under the Chapter 11 subdirectory.
The Machine Configuration File
The configuration files you’ve examined in this chapter have a common theme: they apply only to
a specific application (that is why they have the same name as the launching application). In addition, each .NET-aware machine has a file named machine.config that contains a vast number of configuration details (many of which have nothing to do with resolving external assemblies) that control how the .NET platform operates.
The .NET platform maintains a separate *.config file for each version of the framework installed on the local machine. The machine.config file for .NET 2.0 can be found under the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\CONFIG directory (your version may differ). If you were to open this file, you would find numerous XML elements that control ASP.NET settings, various security details, debugging support, and so forth. However, if you wish to update the machine.config file with machinewide application settings (via an <appSettings> element), you are free to do so.
Although this file can be directly edited using Notepad, be warned that if you alter this file incorrectly, you may cripple the ability of the runtime to function correctly. This scenario can be far more painful than a malformed application *.config file, given that XML errors in an application configuration file affect only a single application, but erroneous XML in the machine.config file can break a specific version of the .NET platform.
The Assembly Binding “Big Picture”
Now that you have drilled down into the details regarding how the CLR resolves the location of requested external assemblies, remember that the simple case is, indeed, simple. Many (if not most) of your .NET applications will consist of nothing more than a group of private assemblies deployed to a single directory. In this case, simply copy the folder to a location of your choosing and run the client executable.
As you have seen, however, the CLR will check for client configuration files and publisher policy assemblies during the resolution process. To summarize the path taken by the CLR to resolve an external assembly reference, ponder Figure 11-29.

C H A P T E R 1 1 ■ I N T R O D U C I N G . N E T A S S E M B L I E S |
389 |
|||
|
|
|
|
|
Figure 11-29. Behold the CLR’s path of assembly resolution.
Summary
This chapter drilled down into the details of how the CLR resolves the location of externally referenced assemblies. You began by examining the content within an assembly: headers, metadata, manifests, and CIL. Then you constructed single-file and multifile assemblies and a handful of client applications (written in a language-agonistic manner).
As you have seen, assemblies may be private or shared. Private assemblies are copied to the client’s subdirectory, whereas shared assemblies are deployed to the Global Assembly Cache (GAC), provided they have been assigned a strong name. Finally, has you have seen, private and shared assemblies can be configured using a client-side XML configuration file or, alternatively, via a publisher policy assembly.



392 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Figure 12-1. Viewing an assembly’s metadata
As you can see, ildasm.exe’s display of .NET type metadata is very verbose (the actual binary format is much more compact). In fact, if I were to list the entire metadata description representing the CarLibrary.dll assembly, it would span several pages. Given that this act would be a woeful waste of your time (and paper), let’s just glimpse into some key types of the CarLibrary.dll assembly.
Viewing (Partial) Metadata for the EngineState Enumeration
Each type defined within the current assembly is documented using a “TypeDef #n” token (where TypeDef is short for type definition). If the type being described uses a type defined within a separate
.NET assembly, the referenced type is documented using a “TypeRef #n” token (where TypeRef is short for type reference). A TypeRef token is a pointer (if you will) to the referenced type’s full metadata definition. In a nutshell, .NET metadata is a set of tables that clearly mark all type definitions (TypeDefs) and referenced entities (TypeRefs), all of which can be viewed using ildasm.exe’s metadata window.
As far as CarLibrary.dll goes, one TypeDef we encounter is the metadata description of the CarLibrary.EngineState enumeration (your number may differ; TypeDef numbering is based on the order in which the C# compiler processes the file):
TypeDef #1
-------------------------------------------------------
TypDefName: |
CarLibrary.EngineState (02000002) |
||
Flags |
: |
[Public] |
[AutoLayout] [Class] [Sealed] [AnsiClass] (00000101) |
Extends |
: |
01000001 |
[TypeRef] System.Enum |
...
Field #2
-------------------------------------------------------
Field Name: engineAlive (04000002)
Flags |
: |
[Public] [Static] [Literal] [HasDefault] (00008056) |
DefltValue: |
(I4) 0 |
|
CallCnvntn: |
[FIELD] |
|
Field |
type: |
ValueClass CarLibrary.EngineState |
...
Here, the TypDefName token is used to establish the name of the given type. The Extends metadata token is used to document the base class of a given .NET type (in this case, the referenced type, System.Enum). Each field of an enumeration is marked using the “Field #n” token. For brevity, I have simply listed the metadata for EngineState.engineAlive.

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
393 |
Viewing (Partial) Metadata for the Car Type
Here is a partial dump of the Car type that illustrates the following:
•How fields are defined in terms of .NET metadata
•How methods are documented via .NET metadata
•How a single type property is mapped to two discrete member functions
TypeDef #3
-------------------------------------------------------
TypDefName: |
CarLibrary.Car (02000004) |
||
Flags |
: |
[Public] |
[AutoLayout] [Class] [Abstract] [AnsiClass] (00100081) |
Extends |
: |
01000002 |
[TypeRef] System.Object |
Field #1
-------------------------------------------------------
Field Name: |
petName (04000008) |
|
Flags |
: |
[Family] (00000004) |
CallCnvntn: |
[FIELD] |
|
Field type: |
String |
|
... |
|
|
Method #1 |
|
|
-------------------------------------------------------
MethodName: .ctor (06000001)
Flags |
: [Public] [HideBySig] [ReuseSlot] [SpecialName] |
[RTSpecialName] [.ctor] (00001886) |
|
RVA |
: 0x00002050 |
ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT]
hasThis ReturnType: Void No arguments.
...
Property #1
-------------------------------------------------------
Prop.Name : |
PetName (17000001) |
|
Flags |
: |
[none] (00000000) |
CallCnvntn: |
[PROPERTY] |
hasThis ReturnType: String No arguments. DefltValue:
Setter |
: |
(06000004) |
set_PetName |
Getter |
: |
(06000003) |
get_PetName |
0 Others |
|
|
|
...
First, note that the Car class metadata marks the type’s base class and includes various flags that describe how this type was constructed (e.g., [public], [abstract], and whatnot). Methods (such as our Car’s constructor) are described in regard to their parameters, return value, and name. Finally, note how properties are mapped to their internal get/set methods using the .NET metadata Setter/Getter tokens. As you would expect, the derived Car types (SportsCar and MiniVan) are described in a similar manner.