
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
513 |
Building Shared Assembly Version 2.0.0.0
Now, open your existing CarLibrary project and update your code base with a new enum named MusicMedia that defines four possible musical devices:
// Holds source of music. public enum MusicMedia
{
musicCd,
musicTape,
musicRadio,
musicMp3
}
As well, add a new public method to the Car type that allows the caller to turn on one of the given media players (be sure to import the System.Windows.Forms namespace within Car.cs if necessary):
public abstract class Car
{
...
public void TurnOnRadio(bool musicOn, MusicMedia mm)
{
if(musicOn)
MessageBox.Show(string.Format("Jamming {0}", mm)); else
MessageBox.Show("Quiet time...");
}
...
}
Update the constructors of the Car class to display a MessageBox that verifies you are indeed using CarLibrary 2.0.0.0:
public abstract class Car
{
...
public Car()
{
MessageBox.Show("CarLibrary Version 2.0!");
}
public Car(string name, short max, short curr)
{
MessageBox.Show("CarLibrary Version 2.0!"); petName = name; maxSpeed = max; currSpeed = curr;
}
...
}
Finally, before you recompile, be sure to update this version of this assembly to 2.0.0.0 by updating the value passed to the [AssemblyVersion] attribute:
// CarLibrary version 2.0.0.0 (now with music!)
[assembly: AssemblyVersion("2.0.0.0")]
If you look in your project’s \bin\Debug folder, you’ll see that you have a new version of this assembly (2.0.0.0), while version 1.0.0.0 is safe in storage under the CarLibrary Version 1.0.0.0 directory. Install this new assembly into the GAC as described earlier in this chapter. Notice that you now have two versions of the same assembly (see Figure 15-25).

514 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
Figure 15-25. Side-by-side execution of a shared assembly
If you were to run the current SharedCarLibClient.exe program by double-clicking the icon using Windows Explorer, you should not see the “CarLibrary Version 2.0!” message box appear, as the manifest is specifically requesting version 1.0.0.0. How then can you instruct the CLR to bind to version 2.0.0.0? Glad you asked!
■Note Visual Studio 2008 will automatically reset references when you compile your applications! Therefore, if you were to run your SharedCarLibClient.exe application within Visual Studio 2008, it will grab CarLibrary.dll version 2.0.0.0! If you accidentally ran your application in this way, simply delete the current CarLibrary.dll reference and select version 1.0.0.0 (which I suggested you place in a folder named CarLibrary Version 1.0.0.0).
Dynamically Redirecting to Specific Versions of a
Shared Assembly
When you wish to inform the CLR to load a version of a shared assembly other than the version listed in its manifest, you may build a *.config file that contains a <dependentAssembly> element. When doing so, you will need to create an <assemblyIdentity> subelement that specifies the friendly name of the assembly listed in the client manifest (CarLibrary, for this example) and an optional culture attribute (which can be assigned an empty string or omitted altogether if you wish to specify the default culture for the machine). Moreover, the <dependentAssembly> element will define a <bindingRedirect> subelement to define the version currently in the manifest (via the oldVersion attribute) and the version in the GAC to load instead (via the newVersion attribute).
Create a new configuration file in the application directory of SharedCarLibClient named SharedCarLibClient.exe.config that contains the following XML data. Of course, the value of your public key token will be different from what you see in the following markup, and it can be obtained either by examining the client manifest using ildasm.exe or via the GAC.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly>

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
515 |
<assemblyIdentity name="CarLibrary" publicKeyToken="219ef380c9348a38" culture="neutral"/>
<bindingRedirect oldVersion= "1.0.0.0" newVersion= "2.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Now run the SharedCarLibClient.exe program by double-clicking the executable from the Windows Explorer. You should see the message that displays version 2.0.0.0 has loaded.
Multiple <dependentAssembly> elements can appear within a client’s configuration file. Although you have no need to do so for this example, assume that the manifest of SharedCarLibClient.exe also referenced version 2.5.0.0 of an assembly named MathLibrary. If you wished to redirect to version 3.0.0.0 of MathLibrary (in addition to version 2.0.0.0 of CarLibrary), the SharedCarLibClient.exe.config file would look like the following:
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Controls Binding to CarLibrary -->
<dependentAssembly> <assemblyIdentity name="CarLibrary"
publicKeyToken="219ef380c9348a38"
culture=""/>
<bindingRedirect oldVersion= "1.0.0.0" newVersion= "2.0.0.0"/> </dependentAssembly>
<!-- Controls Binding to MathLibrary -->
<dependentAssembly>
<assemblyIdentity name="MathLibrary" publicKeyToken="219ef380c9348a38" culture=""/>
<bindingRedirect oldVersion= "2.5.0.0" newVersion= "3.0.0.0"/> </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
■Note It is possible to specify a range of old version numbers via the oldVersion attribute; for example,
<bindingRedirect oldVersion="1.0.0.0-1.2.0.0" newVersion="2.0.0.0"/> informs the CLR to use version 2.0.0.0 for any older version within the range of 1.0.0.0 to 1.2.0.0.
Revisiting the .NET Framework Configuration Utility
As you would hope, you can generate configuration details for shared assemblies using the graphical .NET Framework Configuration utility. Like the process of building a *.config file for private assemblies, the first step is to reference the *.exe to configure. To illustrate, delete the
SharedCarLibClient.exe.config you just authored. Now, add a reference to SharedCarLibClient.exe by right-clicking the Applications node. Once you do, expand the plus sign (+) icon and select the

516CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
Configured Assemblies subnode. From here, click the Configure an Assembly link on the right side of the utility.
At this point, you are presented with a dialog box that allows you to establish a <dependentAssembly> element using a number of friendly UI elements. First, select the “Choose an assembly from the list of assemblies this application uses” radio button and click the Choose Assembly button.
A dialog box now displays that shows you not only the assemblies specifically listed in the client manifest, but also the assemblies referenced by these assemblies. For this example’s purposes, select CarLibrary. When you click the Finish button, you will be shown a Properties page for this one small aspect of the client’s manifest. Here, you can generate the <dependentAssembly> using the Binding Policy tab.
Once you select the Binding Policy tab, you can set the oldVersion attribute (1.0.0.0) via the Requested Version text field and the newVersion attribute (2.0.0.0) using the New Version text field. Once you have committed the settings, you will find the following configuration file is generated for you:
<?xml version="1.0"?> <configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly>
<assemblyIdentity name="CarLibrary" publicKeyToken="219ef380c9348a38" />
<publisherPolicy apply="yes" />
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" /> </dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Investigating the Internal Composition of the GAC
At this point, you have deployed and configured private and shared assemblies. Before we turn to the topic of publisher policy, let’s investigate the internal composition of the GAC itself. When you view the GAC using Windows Explorer, you find a number of icons representing each version of a shared assembly. This graphical shell is provided courtesy of a COM server named shfusion.dll. As you may suspect, however, beneath these icons is an elaborate (but predictable) directory structure.
To understand what the GAC really boils down to, open a command prompt and change to the Assembly directory:
cd c:\windows\assembly
Issue a dir command from the command line. Here you will find a folder named GAC_MISL (see Figure 15-26).

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
517 |
Figure 15-26. The hidden GAC_MSIL subdirectory
Change to the GAC_MSIL directory and issue a dir command once more. You will now be presented with a list of a number of subdirectories that happen to have the same exact name as the icons displayed by shfusion.dll. Change to the CarLibrary subdirectory and again issue a dir command (see Figure 15-27).
Figure 15-27. Inside the hidden CarLibrary subdirectory
As you can see, the GAC maintains a subdirectory for each version of a shared assembly, which follows the naming convention <versionOfAssembly>__PublicKeyToken. If you were again to change the current directory to version 1.0.0.0 of CarLibrary, you would indeed find a copy of the code library (see Figure 15-28).

518 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
Figure 15-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 (which must be entered on a single line within the command window):
al /link: CarLibraryPolicy.xml /out:policy.1.0.CarLibrary.dll /keyf:C:\MyKey\myKey.snk /v:1.0.0.0

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
519 |
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. Using this technique, you are able to design a “machinewide” redirection for all applications using a specific version (or range of versions) of an existing assembly.
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 the GAC of 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 end points, or an arbitrary local directory outside a client’s application directory).
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. 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?). If you are interested, you can view the content of your machine’s download cache by supplying the /ldl option to gacutil.exe:
gacutil /ldl
■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).

520 CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES
To see the <codeBase> element in action, create a Console Application named CodeBaseClient, set a reference to CarLibrary.dll version 2.0.0.0, and update the initial file as follows:
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=" CarLibrary" 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 locate the external assembly under C:\MyAsms.
■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 follows:
<codeBase version="2.0.0.0" href="http://www.IntertechTraining.com/Assemblies/CarLibrary.dll" />

CHAPTER 15 ■ INTRODUCING .NET ASSEMBLIES |
521 |
■Source Code The CodeBaseClient application can be found under the Chapter 15 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 applicationspecific 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 an App.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=
"Data Source=localhost;Initial Catalog=AutoLot;Integrated Security=True" /> <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):
using System.Configuration;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Reading <appSettings> Data *****\n");
//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("Howdy!");
Console.ReadLine();
}
}
The AppSettingsReader class type does not provide a way to write application-specific data to a *.config file. However, if you ever needed to programmatically add new <appSettings> elements to