You can still use the configuration files with shared assemblies. In fact, because the GAC can hold multiple versions of an assembly, configuration files become extremely useful for indicating the version that should be used with a particular client. By default, a client will use only a strongly named assembly if it has the exact same version number as the assembly it was compiled with. If the client cannot find a suitable assembly, a TypeLoadException or FileNotFoundException will occur. Every .NET application defines the components it requires in its manifest, which is a special portion of assembly metadata at the beginning of the compiled file. You can view this information using a handy utility included with the .NET Framework called ILDasm.exe (short for IL Disassembler). You can typically find this utility in a directory such as C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Bin. Figure 2-6 shows ILDasm.exe viewing an ordinary Windows application that contains a single form. Using the assembly metadata, ILDasm.exe can determine all the classes it contains and provide information about their methods, properties, and events. Figure 2-6. Viewing an assembly with ILDasm.exe
If you double-click the MANIFEST entry in the tree, you'll see a text listing like the one shown in Figure 2-7. The .assembly extern statements at the beginning of the manifest indicate the dependent assemblies and the required version numbers. In this case, the only dependencies are for some of the core assemblies included with the .NET Framework and the custom DBComponent. Because these are strong-named assemblies, the .assembly extern statements include a public key token, which is a hash of the assembly's public key. Figure 2-7. Viewing assembly dependencies in the manifest
If the application can't find the required version of a dependent assembly in the GAC, it throws a TypeLoadException or FileNotFoundException (as shown in Figure 2-8). If there are multiple versions of the dependent assembly in the GAC, the application automatically selects the one it was compiled with. Figure 2-8. Failing to find the correct version of a strong-named assembly
Note In an early beta version, .NET supported a feature called Quick Fix Engineering (QFE). Under this system, the assembly loader would require a match only for the major and minor revision numbers and would use the assembly with the highest build and revision numbers. Unfortunately, this system wasn't strict enough. Therefore, the release versions of .NET 1.0 and 1.1 require component versions to match exactly, guaranteeing that DLL Hell can never return. If you want the application to use a different version of a dependent assembly, you have two options. First, you can recompile the client with a new version. This way, the new version number will be embedded in the client's manifest. Alternatively, you can apply a new version policy. Versioning Policies with Strong-Named AssembliesIf you need to override the default binding behavior after an application has been deployed (either to direct a client or to use an older or newer version than it would use by default), you must create a configuration file. In the configuration file, you identify the assembly by name and by its public key token (which is displayed in the GAC). You can then add one or more <bindingRedirect> tags. Each redirect maps a single requested version, or a range of requested versions, to a specific new version. This version must be present in the GAC; otherwise, the application will throw an exception on startup. The configuration file in Listing 2-5 maps versions 1.0.0.0 through 2.9.9.9 of a shared assembly named DBComponent to version 3.0.0.0. Listing 2-5 Redirecting an assembly reference to a new version<?xml version="1.0"?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="DBComponent" publicKeyToken="b03f5f7f11d50a3a"/> <bindingRedirect oldVersion="1.0.0.0-2.9.9.9" newVersion="3.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration> Remember, you can use this technique only with assemblies that have strong names. Otherwise, .NET ignores the version information and assumes that any local assemblies are compatible. Code Bases with Shared AssembliesShared assemblies also give you additional power with the <codeBase> element. Namely, you can specify any file or URL location from which the assembly should be downloaded. This little-considered feature enables you to partially solve deployment problems by allowing the client computer to automatically download the version of an assembly that it needs, according to the configuration file of the client application it is running. Listing 2-6 automatically downloads version 2.0.0.0 of a component from a Web site (provided the client attempts to use this component). Listing 2-6 Automated deployment through downloading<configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="DBComponent" publicKeyToken="b03f5f7f11d50a3a"/> <codeBase version="2.0.0.0" href="http://www.prosetech.com/DBComponent.dll"/> </dependentAssembly> </assemblyBinding> </runtime> </configuration> Note that when you use the <codeBase> tag with a shared assembly, you need to include the URI at the beginning of the address. Therefore, if you want to use a file path, you do it in the form file:///g:\Components\DBComponent.dll or http://MyServer/Components/DBComponent.dll. If the assembly is private, the <codeBase> tag must use a path relative to the application directory. Downloading code in this manner can result in your assembly being placed into a lower security context and not being able to perform the work it needs to do. Before you can successfully use this technique to download assemblies from another computer, you need to modify code-access security settings, as described in Chapter 15. Chapter 15 also provides more information about automated deployment and downloading and presents some flexible techniques that don't require strong names. Note You can create and tweak configuration files using the .NET Framework Configuration tool that is included with the SDK. Just choose Settings, Control Panel, Administrative Tools, Microsoft .NET Framework Configuration from the Start menu. Among other things, this tool enables you to configure versioning policies and code bases for an application. A Final Word About Assembly BindingThe assembly binding process can be complex, particularly for shared components. Figure 2-9 shows a high-level overview of the process. You can use other .NET Framework features to configure how assembly binding takes place in even greater detail. For example, you can develop publisher policy or machine policy files that specify more generic assembly binding rules. However, these options (which are described on MSDN) continuously raise new versioning headaches. In a distributed application, the best deployment approaches don't require per-client configuration, which can complicate the setup immensely and introduce new problems when components are updated in the future. Ideally, you will sidestep these issues by using private assemblies whenever possible. In a perfect world, the only consumer of a shared assembly will be a server-side component. This ensures that you can apply new version policies, if necessary, in a controlled environment. In a world with distributed clients, enforcing these policies might not be as easy. Figure 2-9. The assembly binding process
If you encounter an assembly binding problem that you can't resolve, you can use the Assembly Binding Log Viewer utility included with Visual Studio .NET (look for the file fuslogvw.exe, which you can run from the command line). This utility can be configured to track failed assembly binding attempts for applications in a specific directory, or across the entire computer. |