Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

.pdf
Скачиваний:
111
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать

374 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-18. A strongly named assembly records the public key in the manifest.

Assigning Strong Names Using Visual Studio 2005

Before you deploy CarLibrary.dll to the GAC, let me point out that Visual Studio 2005 allows you to specify the location of your *.snk file using the project’s Properties page (in fact, this is now considered the preferred approach; using the [AssemblyKeyFile] attribute generates a compiler warning under Visual Studio 2005). To do so, select the Signing node, supply the path to the *.snk file, and select the “Sign the assembly” check box (see Figure 11-19).

Figure 11-19. Specifying a *.snk file via the Properties page

Installing/Removing Shared Assemblies to/from the GAC

The final step is to install the (now strongly named) CarLibrary.dll into the GAC. The simplest way to install a shared assembly into the GAC is to drag and drop the assembly to C:\Windows\Assembly using Windows Explorer (which is ideal for a quick test).

In addition, the .NET Framework 2.0 SDK provides a command-line utility named gacutil.exe that allows you to examine and modify the contents of the GAC. Table 11-1 documents some relevant options of gacutil.exe (specify the /? flag to see each option).

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

375

Table 11-1. Various Options of gacutil.exe

Option

Meaning in Life

/i

Installs a strongly named assembly into the GAC

/u

Uninstalls an assembly form the GAC

/l

Displays the assemblies (or a specific assembly) in the GAC

 

 

Using either technique, deploy CarLibrary.dll to the GAC. Once you’ve finished, you should see your library present and accounted for (see Figure 11-20).

Figure 11-20. The strongly named, shared CarLibrary (version 1.0.0.0)

Note You may right-click any assembly icon to pull up its Properties page, and you may also uninstall a specific version of an assembly altogether from the right-click context menu (the GUI equivalent of supplying the /u flag to gacutil.exe).

The Role of Delayed Signing

When you are building your own custom .NET assemblies, you are able to assign a strong name using your own personal *.snk file. However, given the sensitive nature of a public/private key file, don’t be too surprised if your company/department refuses to give you access to the master *.snk file. This is an obvious problem, given that we (as developers) will often need to install an assembly into the GAC for testing purposes. To allow this sort of testing (while not distributing the true *.snk file), you are able to make use of delayed signing. We have no need to do so for the current CarLibrary.dll; however, here is an overview of the process.

Delayed signing begins by the trusted individual holding the *.snk file extracting the public key value from the public/private *.snk file using the -p command-line flag of sn.exe, to produce a new file that only contains the public key value:

sn -p myKey.snk testPublicKey.snk

At this point, the testPublicKey.snk file can be distributed to individual developers for the creation and testing of strongly named assemblies. To inform the C# compiler that the assembly in question is making use of delayed signing, the developer must make sure to set the value of the AssemblyDelaySign attribute to true in addition to specifying the pseudo-key file as the parameter to the AssemblyKeyFile attribute. Here are the relevant updates to the project’s AssemblyInfo.cs file:

[assembly: AssemblyDelaySign(true)]

[assembly: AssemblyKeyFile(@"C:\MyKey\testPublicKey.snk)]

Note If you are using Visual Studio 2005, these same attributes can be established visually using the Properties page of your project.

Once an assembly has enabled delayed signing, the next step is to disable the signature verification process that happens automatically when an assembly is deployed to the GAC. To do so, specify the -vr flag (using sn.exe) to skip the verification process on the current machine:

sn.exe -vr MyAssembly.dll

Once all testing has been performed, the assembly in question can be shipped to the trusted individual who holds the “true” public/private key file to resign the binary to provide the correct digital signature. Again, sn.exe provides the necessary behavior, this time using the -r flag:

sn.exe -r MyAssembly.dll C:\MyKey\myKey.snk

To enable the signature verification process, the final step is to apply the -vu flag:

sn.exe -vu MyAssembly.dll

Understand, of course, that if you (or your company) only build assemblies intended for internal use, you may never need to bother with the process of delayed signing. However, if you are in the business of building .NET assemblies that may be purchased by external parties, the ability to delay signing keeps things safe and sane for all involved.

Consuming a Shared Assembly

When you are building applications that make use of a shared assembly, the only difference from consuming a private assembly is in how you reference the library using Visual Studio 2005. In reality, there is no difference as far as the tool is concerned (you still make use of the Add Reference dialog box). What you must understand is that this dialog box will not allow you to reference the assembly by browsing to the Assembly folder. Any efforts to do so will be in vain, as you cannot reference the assembly you have highlighted (see Figure 11-21).

Figure 11-21. Nope! You can’t reference shared assemblies by navigating to the Assembly folder using Visual Studio 2005.

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

377

Rather, you will need to browse to the \Bin\Debug directory of the original project via the Browse tab (see Figure 11-22).

Figure 11-22. Correct! You must reference shared assemblies by navigating to the project’s \Bin\Debug directory using Visual Studio 2005.

This (somewhat annoying) fact aside, create a new C# console application named SharedCarLibClient and exercise your types as you wish:

using CarLibrary;

namespace SharedCarLibClient

{

class Program

{

static void Main(string[] args)

{

SportsCar c = new SportsCar(); c.TurboBoost(); Console.ReadLine();

}

}

}

Once you have compiled your client application, navigate to the directory that contains SharedCarLibClient.exe using Windows Explorer and notice that Visual Studio 2005 has not copied CarLibrary.dll to the client’s application directory. When you reference an assembly whose manifest contains a .publickey value, Visual Studio 2005 assumes the strongly named assembly will most likely be deployed in the GAC, and therefore does not bother to copy the binary.

As a quick side note, if you wish to have Visual Studio 2005 copy a shared assembly to the client directory, you can select an assembly from the References node of Solution Explorer and set the Copy Local property to True or False using the Properties window (see Figure 11-23).

378 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-23. The Copy Local property can force a copy of a strongly named code library.

Exploring the Manifest of SharedCarLibClient

Recall that when you generate a strong name for an assembly, the entire public key is recorded in the assembly manifest. On a related note, when a client references a strongly named assembly, its manifest records a condensed hash-value of the full public key, denoted by the .publickeytoken tag. If you were to open the manifest of SharedCarLibClient.exe using ildasm.exe, you would find the following:

.assembly extern CarLibrary

{

.publickeytoken = (21 9E F3 80 C9 34 8A 38)

.ver 1:0:0:0

}

If you compare the value of the public key token recorded in the client manifest with the public key token value shown in the GAC, you will find a dead-on match. Recall that a public key represents one aspect of the strongly named assembly’s identity. Given this, the CLR will only load version 1.0.0.0 of an assembly named CarLibrary that has a public key that can be hashed down to the value 219EF380C9348A38. If the CLR does not find an assembly meeting this description in the GAC (and cannot find a private assembly named CarLibrary in the client’s directory), a FileNotFound exception is thrown.

Source Code The SharedCarLibClient application can be found under the Chapter 11 subdirectory.

Configuring Shared Assemblies

Like a private assembly, shared assemblies can be configured using a client *.config file. Of course, because shared assemblies are found in a well-known location (the GAC), you will not specify a <privatePath> element as you did for private assemblies (although if the client is using both shared and private assemblies, the <privatePath> element may still exist in the *.config file).

You can use application configuration files in conjunction with shared assemblies whenever you wish to instruct the CLR to bind to a different version of a specific assembly, effectively bypassing the

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

379

value recorded in the client’s manifest. This can be useful for a number of reasons. For example, imagine that you have shipped version 1.0.0.0 of an assembly and discover a major bug some time after the fact. One corrective action would be to rebuild the client application to reference the correct version of the bug-free assembly (say, 1.1.0.0) and redistribute the updated client and new library to each and every target machine.

Another option is to ship the new code library and a *.config file that automatically instructs the runtime to bind to the new (bug-free) version. As long as the new version has been installed into the GAC, the original client runs without recompilation, redistribution, or fear of having to update your resume.

Here’s another example: you have shipped the first version of a bug-free assembly (1.0.0.0), and after a month or two, you add new functionality to the assembly in question to yield version 2.0.0.0. Obviously, existing client applications that were compiled against version 1.0.0.0 have no clue about these new types, given that their code base makes no reference to them.

New client applications, however, wish to make reference to the new functionality found in version 2.0.0.0. Under .NET, you are free to ship version 2.0.0.0 to the target machines, and have version 2.0.0.0 run alongside the older version 1.0.0.0. If necessary, existing clients can be dynamically redirected to load version 2.0.0.0 (to gain access to the implementation refinements), using an application configuration file without needing to recompile and redeploy the client application.

Freezing the Current Shared Assembly

To illustrate how to dynamically bind to a specific version of a shared assembly, open Windows Explorer and copy the current version of CarLibrary (1.0.0.0) into a distinct subdirectory (I called mine “Version 1”) off the project root to symbolize the freezing of this version (see Figure 11-24).

Figure 11-24. Freezing the current version of CarLibrary.dll

Building Shared Assembly Version 2.0.0.0

Now, update your CarLibrary project to define a new enum named MusicMedia that defines four possible musical devices:

// Holds source of music. public enum MusicMedia

{

musicCd,

musicTape,

musicRadio,

musicMp3

}

380 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

As well, add a new public method to the Car type that allows the caller to turn on one of the given media players:

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("Car 2.0.0.0");

}

public Car(string name, short max, short curr)

{

MessageBox.Show("Car 2.0.0.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 Version 1 subdirectory. 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 11-25).

Figure 11-25. Side-by-side execution

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

381

If you were to run the current SharedCarLibClient.exe program by double-clicking the icon using Windows Explorer, you should not see the “Car 2.0.0.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.

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 code, 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>

<assemblyIdentity name="CarLibrary" publicKeyToken="219ef380c9348a38" culture=""/>

<bindingRedirect oldVersion= "1.0.0.0" newVersion= "2.0.0.0"/>

</dependentAssembly>

</assemblyBinding>

</runtime>

</configuration>

Now run the SharedCarLibClient.exe program. You should see the message that displays version 2.0.0.0 has loaded. If you set the newVersion attribute to 1.0.0.0 (or if you simply deleted the *.config file), you now see the message that version 1.0.0.0 has loaded, as the CLR found version 1.0.0.0 listed in the client’s manifest.

Multiple <dependentAssembly> elements can appear within a client’s configuration file. Although you have no need to do so, 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"> <dependentAssembly>

<assemblyIdentity name="CarLibrary" publicKeyToken="219ef380c9348a38" culture=""/>

<bindingRedirect oldVersion= "1.0.0.0" newVersion= "2.0.0.0"/>

</dependentAssembly>

382 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

<dependentAssembly>

<assemblyIdentity name="MathLibrary" publicKeyToken="219ef380c9348a38" culture=""/>

<bindingRedirect oldVersion= "2.5.0.0" newVersion= "3.0.0.0"/>

</dependentAssembly>

</assemblyBinding>

</runtime>

</configuration>

Revisiting the .NET Framework 2.0 Configuration Utility

As you would hope, you can generate shared assembly–centric *.config files using the graphical .NET Framework 2.0 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 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 (which simply means, “Show me the manifest!”) 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

So far, so good. Now, let’s dig into 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

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

383

Issue a dir command from the command line. Here you will find a folder named GAC_MISL (see Figure 11-26).

Figure 11-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 11-27).

Figure 11-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 11-28).