
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
364 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
Exploring the ufo.netmodule File
Now, using ildasm.exe, open ufo.netmodule. As you can see, *.netmodules contain a module-level manifest; however, its sole purpose is to list each external assembly referenced by the code base. Given that the Ufo class did little more than make a call to Console.WriteLine(), you find the following:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.module ufo.netmodule
Exploring the airvehicles.dll File
Next, using ildasm.exe, open the primary airvehicles.dll module and investigate the assembly-level manifest. Notice that the .file token documents the associated modules in the multifile assembly (ufo.netmodule in this case). The .class extern tokens are used to document the names of the external types referenced for use from the secondary module (Ufo):
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 2:0:0:0
}
.assembly airvehicles
{
...
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.file ufo.netmodule
...
.class extern public AirVehicles.Ufo
{
.file ufo.netmodule
.class 0x02000002
}
.module airvehicles.dll
Again, realize that the only entity that links together airvehicles.dll and ufo.netmodule is the assembly manifest. These two binary files have not been merged into a single, larger *.dll.
Consuming a Multifile Assembly
The consumers of a multifile assembly couldn’t care less that the assembly they are referencing is composed of numerous modules. To keep things simple, let’s create a new Visual Basic .NET client application at the command line. Create a new file named Client.vb with the following Module definition. When you are done, save it in the same location as your multifile assembly.
Imports AirVehicles
Module Module1 Sub Main()
Dim h As New AirVehicles.Helicopter() h.TakeOff()


366C 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
that contains the assembly’s manifest minus the file extension. For example, if you examine the manifest of the CarLibrary.dll assembly, you find the following (your version will no doubt differ):
.assembly CarLibrary
{
...
.ver 1:0:454:30104
}
Given the isolated nature of a private assembly, it should make sense that the CLR does not bother to make use of the version number when resolving its location. The assumption is that private assemblies do not need to have any elaborate version checking, as the client application is the only entity that “knows” of its existence. Given this, it is (very) possible for a single machine to have multiple copies of the same private assembly in various application directories.
Understanding the Probing Process
The .NET runtime resolves the location of a private assembly using a technique termed probing, which is much less invasive than it sounds. Probing is the process of mapping an external assembly request to the location of the requested binary file. Strictly speaking, a request to load an assembly may be either implicit or explicit. An implicit load request occurs when the CLR consults the manifest in order to resolve the location of an assembly defined using the .assembly extern tokens:
// An implicit load request.
.assembly extern CarLibrary
{ ...}
An explicit load request occurs programmatically using the Load() or LoadFrom() method of the System.Reflection.Assembly class type, typically for the purposes of late binding and dynamic invocation of type members. You’ll examine these topics further in Chapter 12, but for now you can see an example of an explicit load request in the following code:
// An explicit load request.
Assembly asm = Assembly.Load("CarLibrary");
In either case, the CLR extracts the friendly name of the assembly and begins probing the client’s application directory for a file named CarLibrary.dll. If this file cannot be located, an attempt is made to locate an executable assembly based on the same friendly name (CarLibrary.exe). If neither of these files can be located in the application directory, the runtime gives up and throws a FileNotFound exception at runtime.
■Note Technically speaking, if a copy of the requested assembly cannot be found within the client’s application directory, the CLR will also attempt to locate a client subdirectory with the exact same name as the assembly’s friendly name (e.g., C:\MyClient\CarLibrary). If the requested assembly resides within this subdirectory, the CLR will load the assembly into memory.
Configuring Private Assemblies
While it is possible to deploy a .NET application by simply copying all required assemblies to a single folder on the user’s hard drive, you will most likely wish to define a number of subdirectories to group related content. For example, assume you have an application directory named C:\MyApp that contains CSharpCarClient.exe. Under this folder might be a subfolder named MyLibraries that contains CarLibrary.dll.

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 |
367 |
Regardless of the intended relationship between these two directories, the CLR will not probe the MyLibraries subdirectory unless you supply a configuration file. Configuration files contain various XML elements that allow you to influence the probing process. By “law,” configuration files must have the same name as the launching application and take a *.config file extension, and they must be deployed in the client’s application directory. Thus, if you wish to create a configuration file for CSharpCarClient.exe, it must be named CSharpCarClient.exe.config.
To illustrate the process, create a new directory on your C drive named MyApp using Windows Explorer. Next, copy CSharpCarClient.exe and CarLibrary.dll to this new folder, and run the program by double-clicking the executable. Your program should run successfully at this point (remember, the assemblies are not registered!). Next, create a new subdirectory under C:\MyApp named MyLibraries (see Figure 11-11), and move CarLibrary.dll to this location.
Figure 11-11. CarLibrary.dll now resides under the MyLibraries subdirectory.
Try to run your client program again. Because the CLR could not locate “CarLibrary” directly within the application directory, you are presented with a rather nasty unhandled FileNotFound exception.
To rectify the situation, create a new configuration file named CSharpCarClient.exe.config and save it in the same folder containing the CSharpCarClient.exe application, which in this example would be C:\MyApp. Open this file and enter the following content exactly as shown (be aware that XML is case sensitive!):
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <probing privatePath="MyLibraries"/>
</assemblyBinding>
</runtime>
</configuration>
.NET *.config files always open with a root element named <configuration>. The nested <runtime> element may specify an <assemblyBinding> element, which nests a further element named <probing>. The privatePath attribute is the key point in this example, as it is used to specify the subdirectories relative to the application directory where the CLR should probe.

368 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
Do note that the <probing> element does not specify which assembly is located under a given subdirectory. In other words, you cannot say, “CarLibrary is located under the MyLibraries subdirectory, but MathUtils is located under Bin subdirectory.” The <probing> element simply instructs the CLR to investigate all specified subdirectories for the requested assembly until the first match is encountered.
■Note Be very aware that the privatePath attribute cannot be used to specify an absolute (C:\SomeFolder\ SomeSubFolder) or relative (..\\SomeFolder\\AnotherFolder) path! If you wish to specify a directory outside the client’s application directory, you will need to make use of a completely different XML element named <codeBase> (more details on this element later in the chapter).
Multiple subdirectories can be assigned to the privatePath attribute using a semicolon-delimited list. You have no need to do so at this time, but here is an example that informs the CLR to consult the MyLibraries and MyLibraries\Tests client subdirectories:
<probing privatePath="MyLibraries; MyLibraries\Tests"/>
Once you’ve finished creating CSharpCarClient.exe.config, run the client by double-clicking the executable in Windows Explorer. You should find that CSharpCarClient.exe executes without a hitch (if this is not the case, double-check it for typos).
Next, for testing purposes, change the name of your configuration file (in one way or another) and attempt to run the program once again. The client application should now fail. Remember that *.config files must be prefixed with the same name as the related client application. By way of a final test, open your configuration file for editing and capitalize any of the XML elements. Once the file is saved, your client should fail to run once again (as XML is case sensitive).
Configuration Files and Visual Studio 2005
While you are always able to create XML configuration files by hand using your text editor of choice, Visual Studio 2005 allows you create a configuration file during the development of the client program. To illustrate, load the CSharpCarClient solution into Visual Studio 2005 and insert a new Application Configuration File item using the Project Add New Item menu selection. Before you click the OK button, take note that the file is named App.config (don’t rename it!). If you look in the Solution Explorer window, you will now find App.config has been inserted into your current project (see Figure 11-12).
Figure 11-12. The Visual Studio 2005 App.config file
At this point, you are free to enter the necessary XML elements for the client you happen to be creating. Now, here is the cool thing. Each time you compile your project, Visual Studio 2005 will

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 |
369 |
automatically copy the data in App.config to the \Bin\Debug directory using the proper naming convention (such as CSharpCarClient.exe.config). However, this behavior will happen only if your configuration file is indeed named App.config.
Using this approach, all you need to do is maintain App.config, and Visual Studio 2005 will ensure your application directory contains the latest and greatest content (even if you happen to rename your project).
Introducing the .NET Framework 2.0 Configuration Utility
Although authoring a *.config file by hand is not too traumatic, the .NET Framework 2.0 SDK does ship with a tool that allows you to build XML configuration files using a friendly GUI. You can find the .NET Framework 2.0 Configuration utility under the Administrative folder of your Control Panel. Once you launch this tool, you will find a number of configuration options (see Figure 11-13).
Figure 11-13. The .NET Framework 2.0 Configuration utility
To build a client *.config file using this utility, your first step is to add the application to configure by right-clicking the Applications node and selecting Add. In the resulting dialog box, you may find the application you wish to configure, provided that you have executed it using Windows Explorer. If this is not the case, click the Other button and navigate to the location of the client program you wish to configure. For this example, select the VbNetCarClient.exe application created earlier in this chapter (look under the Bin folder). Once you have done so, you will now find a new subnode, as shown in Figure 11-14.

370 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-14. Preparing to configure VbNetCarClient.exe
If you right-click the VbNetCarClient node and activate the Properties page, you will notice a text field located at the bottom of the dialog box where you can enter the values to be assigned to the privatePath attribute. Just for testing purposes, enter a subdirectory named TestDir (see Figure 11-15).
Figure 11-15. Configuring a private probing path graphically
Once you click the OK button, you can examine the VbNetCarClient\Debug directory and find that the default *.config file (which Visual Studio 2005 provides for most VB .NET programs) has been updated with the correct <probing> element.



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 |
373 |
Understand that the actual private key data is not listed anywhere within the manifest, but is used only to digitally sign the contents of the assembly (in conjunction with the generated hash code). Again, the whole idea of making use of public/private key cryptography is to ensure that no two companies, departments, or individuals have the same identity in the .NET universe. In any case, once the process of assigning a strong name is complete, the assembly may be installed into the GAC.
■Note Strong names also provide a level of protection against potential evildoers tampering with your assembly’s contents. Given this point, it considered a .NET best practice to strongly name every assembly regardless if it is deployed to the GAC.
Strongly Naming CarLibrary.dll
Let’s walk through the process of assigning a strong name to the CarLibrary assembly created earlier in this chapter (go ahead and open up that project using your IDE of choice). The first order of business is to generate the required key data using the sn.exe utility. Although this tool has numerous command-line options, all you need to concern yourself with for the moment is the -k flag, which instructs the tool to generate a new file containing the public/private key information. Create a new folder on your C drive named MyTestKeyPair and change to that directory using the .NET Command Prompt. Now, issue the following command to generate a file named MyTestKeyPair.snk:
sn -k MyTestKeyPair.snk
Now that you have your key data, you need to inform the C# compiler exactly where MyTestKeyPair.snk is located. When you create any new C# project workspace using Visual Studio 2005, you will notice that one of your initial project files (located under the Properties node of Solution Explorer) is named AssemblyInfo.cs. This file contains a number of attributes that describe the assembly itself. The AssemblyKeyFile assembly-level attribute can be used to inform the compiler of the location of a valid *.snk file. Simply specify the path as a string parameter, for example:
[assembly: AssemblyKeyFile(@"C:\MyTestKeyPair\MyTestKeyPair.snk")]
Given that the version of a shared assembly is one aspect of a strong name, let’s also specify a specific version number for CarLibrary.dll. In the AssemblyInfo.cs file, you will find another attribute named AssemblyVersion. Initially the value is set to 1.0.*:
[assembly: AssemblyVersion("1.0.*")]
Recall that a .NET version number is composed of the four parts (<major>.<minor>.<build>. <revision>). Until you say otherwise, Visual Studio 2005 automatically increments the build and revision numbers (as marked by the * wildcard token) as part of each compilation. To enforce a fixed value for the assembly’s build version, replace the wildcard token with a specific build and revision value:
//Format: <Major version>.<Minor version>.<Build number>.<Revision>
//Valid values for each part of the version number are between 0 and 65535.
[assembly: AssemblyVersion("1.0.0.0")]
At this point, the C# compiler has all the information needed to generate strong name data (as you are not specifying a unique culture value via the [AssemblyCulture] attribute, you “inherit” the culture of your current machine). Compile your CarLibrary code library and open the manifest using ildasm.exe. You will now see a new .publickey tag is used to document the full public key information, while the .ver token records the version specified via the [AssemblyVersion] attribute (see Figure 11-18).