Versioning Issues

In COM, the versioning of DLLs had some significant limitations. For example, a different DLL with the same nominal version number could be indistinguishable from the one desired.

.NET's versioning scheme was specifically designed to alleviate the problems of COM. The major capabilities of .NET that solve versioning issues are as follows:

  • Application isolation
  • Side-by-side execution
  • Self-describing components

Application Isolation

For an application to be isolated, it should be self-contained and independent. This means that the application should rely on its own dependencies for ActiveX controls, components, or files, and not share those files with other applications. The option of having application isolation is essential for a good solution to versioning problems.

If an application is isolated, components are owned, managed, and used by the parent application alone. If a component is used by another application, even if it is the same version, the other application must have its own copy. This ensures that each application can install and uninstall dependencies and not interfere with other applications.


Note
Does this sound familiar? Gray-haired people may recall that this is what most early Windows and DOS applications did until COM required registration of DLLs in the registry and placement of shared DLLs in the system directory.

The .NET Framework enables application isolation by allowing developers to create application-private assemblies. These are in the application's own directory; and if another application needs the same assembly, it can be duplicated in that application's directory.

This means that each application is independent from the others. This isolation works best for many scenarios. It is sometimes referred to as a zero-impact deployment because when you either install or uninstall such an application, you are in no danger of causing problems for any other application.

Side-By-Side Execution

Side-by-side execution occurs when multiple versions of the same assembly can run at the same time. Side-by-side execution is performed by the CLR. Components that are to execute side by side must be installed within the application directory or a subdirectory of it.

With application assemblies, versioning is not much of an issue. The interfaces are dynamically resolved by the CLR. If you replace an application assembly with a different version, the CLR will load it and make it work with the other assemblies in the application, as long as the new version doesn't have any interface incompatibilities. The new version may even have interface elements that are new and therefore don't exist in the old version (new properties or methods). As long as the existing class interface elements used by the other application assemblies are unchanged, the new version will work fine. In the following discussion of exactly how the CLR locates a referenced assembly, you'll learn more about how this works.

Self-Describing Components

In the earlier section on the manifest, the self-describing nature of .NET assemblies was mentioned. The term “self-describing” means that all the information the CLR needs to know to load and execute an assembly is inside the assembly itself.

Self-describing components are essential to side-by-side execution. Once the CLR knows which version is needed, everything else about the assembly needed to run side by side is in the assembly itself. Each application can get its own version of an assembly, and all the work to coordinate the versions in memory is performed transparently by the CLR.

Versioning becomes more important with shared assemblies. Without good coordination of versions, .NET applications with shared assemblies are subject to some of the same problems as COM applications. In particular, if a new version of a shared assembly is placed in the GAC, then there must be a means to control which applications get which version of a shared assembly. This is accomplished with a versioning policy.

Version Policies

As discussed earlier, a version number includes four parts: major, minor, build, and revision. The version number is part of the identity of the assembly. When a new version of a shared assembly is created and placed in the GAC, any of these parts can change. Which ones change affects how the CLR views compatibility for the new assembly.

When the version number of a component only changes according to its build and revision parts, it is considered compatible. This is often referred to as Quick Fix Engineering (QFE). It's only necessary to place the new assembly in the GAC, and it will automatically be considered compatible with applications that were created to use the folder version, even though those applications are expecting a different build number and revision. If either the major or minor build number changes, however, compatibility is over.

When an application comes across a type that is implemented in an external reference, the CLR has to determine what version of the referenced assembly to load. What steps does the CLR go through to ensure that the correct version of an assembly is loaded? To answer this question, you need to understand version policies and how they affect which version of an assembly is loaded.

The Default Versioning Policy

Let's start by looking at the default versioning policy. This policy is followed in the absence of any configuration files that would modify the versioning policy. The runtime default behavior is to consult the manifest for the name of the referenced assembly and the version of the assembly to use.

If the referenced assembly does not contain a strong name, then it is assumed that the referenced assembly is application-private and is located in the application's directory. The CLR takes the name of the referenced assembly and appends .dll to create the filename that contains the referenced assembly's manifest. The CLR then searches in the application's directory for the filename. If it's found, then it uses the version indicated, even if the version number is different from the one specified in the manifest. Therefore, the version numbers of application-private assemblies are not checked, because the application developer, in theory, has control over which assemblies are deployed to the application's directory. If the file cannot be found, the CLR raises a System .IO.FileNotFoundException.

Loading Assemblies

Unlike the default policy, which relies on an assembly being located in the local folder, if the referenced assembly contains a strong name, the process by which an assembly is loaded is different. The CLR instead goes through a process of checking multiple possible locations for the correct copy of the assembly to load. This process follows the following steps.

1. The three different types of assembly configuration files (discussed later) are consulted, if they exist, to see whether they contain any settings that will modify which version of the assembly the CLR should load.
2. The CLR then checks whether the assembly has been requested and loaded in a previous call. If it has, it uses the loaded assembly.
3. If the assembly is not already loaded, then the GAC is queried for a match. If a match is found, it is used by the application.
4. If any of the configuration files contains a codebase (discussed later) entry for the assembly, then the assembly is looked for in the location specified. If the assembly cannot be found in the location specified in the codebase, then a TypeLoadException is raised to the application.
5. If there are no configuration files or no codebase entries for the assembly, then the CLR probes for the assembly starting in the application's base directory.
6. If the assembly still isn't found, then the CLR asks the Windows Installer service if it has the assembly in question. If it does, then the assembly is installed and the application uses it. This is a feature called on-demand installation.

If the assembly hasn't been found by the end of this entire process, then a TypeLoadException is raised.

Although a referenced assembly contains a strong name, this does not mean that it has to be deployed into the GAC. This enables application developers to install a version with the application that is known to work. The GAC is consulted to see whether it contains a version of an assembly with a higher build revision number to enable administrators to deploy an updated assembly without having to reinstall or rebuild the application.

Configuration Files

The default versioning policy described earlier may not be the most appropriate policy for your requirements. Fortunately, you can modify this policy through the use of XML configuration files to meet your specific needs. Two types of configuration files can hold versioning information:

1. The first is an application configuration file, and it is created in the application directory. As the name implies, this configuration file applies to a single application only. You need to create the application configuration file in the application directory with the same name as the application filename and append .config. For example, if you have a Windows Forms application called HelloWorld.exe installed in the C:HelloWorld directory, then the application configuration file would be C:HelloWorldHelloWorld.exe.config. Note the app .config file in your project is this file. At compilation that file is copied to the folder you're your application and given the appropriate name.
2. The second type of configuration file is called the machine configuration file. It is named machine.config and can be found in the C:WindowsMicrosoft.NETFrameworkv4.0.xxxxCONFIG directory. The machine.config file overrides any other configuration files on a machine and can be thought of as containing global settings. It is not considered best practice to change this file for a single application.

The main purpose of configuration files is to provide binding-related information to the developer or administrator who wishes to override the default policy handling of the CLR.

The configuration file is in XML and has a root node named <configuration>. The configuration file is divided into specific types of nodes that represent different areas of control. These areas are as follows:

  • Startup
  • Runtime
  • Remoting
  • Crypto
  • Class API
  • Security

Although all of these areas are important, this chapter covers only the first two. All of the settings discussed can be added to the application configuration file. Some of the settings (these are pointed out) can also be added to the machine configuration file. If a setting in the application configuration file conflicts with one in the machine configuration file, then the setting in the machine configuration file is used. When talking about assembly references in the following discussion of configuration settings, this refers exclusively to shared assemblies (which implies that the assemblies have a strong name, as required by assemblies in the GAC).

Startup Settings

The <startup> node of the application and machine configuration files has a <supportedRuntime> node that specifies the runtime version required by the application. This is because different versions of the CLR can run on a machine side by side. The following example shows how Visual Studio uses the configuration file to identify the correct version of the .NET runtime to host the application at runtime.

<configuration>
  <startup>
    <supportedRuntime version ="4.0 " sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>

Runtime Settings

The runtime node, which is written as <runtime>, can be used to specify settings that manage how the CLR handles garbage collection and versions of assemblies. With these settings, you can specify which version of an assembly the application requires, or redirect it to another version entirely.

Loading a Particular Version of an Assembly

The application and machine configuration files can be used to ensure that a particular version of an assembly is loaded. In those situations where the default load order might result in loading an incompatible version of your DLL, you can indicate the specific version that should be loaded. This functionality is supported through the use of the <assemblyIdentity> and <bindingRedirect> elements in the configuration file. The following snippet demonstrates how these nodes would look within your project's app.config file.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="AssemblyName"
                          publickeytoken="b77a5c561934e089"
                          culture="en-us"/>
          <bindingRedirect oldVersion="*"
                           newVersion="2.1.50.0"/>

      </dependentAssembly>
    </assemblyBindings>
  </runtime>
</configuration>

The <assemblyBinding> node is used to declare settings for the locations of assemblies and redirections via the <dependentAssembly> node and the <probing> node (which you will look at shortly).

In the last example, when the CLR resolves the reference to the assembly named AssemblyName, it loads version 2.1.50.0 instead of the version that appears in the manifest. If you want to load only version 2.1.50.0 of the assembly when a specific version is referenced, for example version 2.0.25.0 , then you can replace the value of the oldVersion attribute with the version number that you would like to replace. In that situation, if the application was still referencing version 2.0.0.0 by default, the application would still reference that version. The publickeytoken attribute is used to store the hash of the strong name of the assembly to replace. This ensures that the correct assembly is identified. The same is true of the culture attribute.

Defining the Location of an Assembly

The location of an assembly can also be defined in both the application and machine configuration files. You can use the <codeBase> element to inform the CLR of the location of an assembly. This enables you to distribute an application and have the externally referenced assemblies downloaded the first time they are used (on-demand downloading):

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="AssemblyName"
                          publickeytoken="b77a5c561934e089"
                          culture="en-us"/>
        <codeBase version="2.1.50.0"
                  href="http://www.wrox.com/AssemblyName.dll/>
      </dependentAssembly>
    </assemblyBindings>
  </runtime>
</configuration>

You can see from this example that whenever a reference to version 2.1.50.0 of the assembly AssemblyName is resolved (and the assembly isn't already on the user's computer), the CLR will try to load the assembly from the location defined in the href attribute. The location defined in the href attribute is a standard URL and can be used to locate a file across the Internet or locally.

If the assembly cannot be found or the details in the manifest of the assembly defined in the href attribute do not match those defined in the configuration file, then the loading of the assembly will fail and you will receive a TypeLoadException. If the version of the assembly in the preceding example were actually 2.1.60.0, then the assembly would load because the version number is only different by build and revision number.

Providing the Search Path

The final use of configuration files to consider is that of providing the search path to use when locating assemblies in the application's directory. This setting applies only to the application configuration file (AppName.exe.config, for example). By default, the CLR searches for an assembly only in the application's base directory — it will not look in any subdirectories. You can modify this behavior by using the <probing> element in an application configuration file, as shown in the following example:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="regional"/>
    </assemblyBinding>
  </runtime>
</configuration>

The privatePath attribute can contain a list of directories relative to the application's directory (separated by a semicolon) that you would like the CLR to search when trying to locate an assembly. The privatePath attribute cannot contain an absolute pathname.

As part of resolving an assembly reference, the CLR checks in the application's base directory for it. If it cannot find it, then it looks through, in order, all the subdirectories specified in the privatePath variable, as well as looking for a subdirectory with the same name as the assembly. If the assembly being resolved is called AssemblyName, then the CLR also checks for the assembly in a subdirectory called AssemblyName, if it exists.

This isn't the end of the story, though. If the referenced assembly being resolved contains a culture setting, then the CLR also checks for culture-specific subdirectories in each of the directories it searches in. For example, if the CLR is trying to resolve a reference to an assembly named AssemblyName with a culture of en and a privatePath equal to that in the last example, and the application being run has a home directory of C:ExampleApp, then the CLR will look in the following directories (in the order shown):

  • C:ExampleApp
  • C:ExampleAppen
  • C:ExampleAppenAssemblyName
  • C:ExampleApp egionalen
  • C:ExampleApp egionalenAssemblyName

As you can see, the CLR can probe quite a number of directories to locate an assembly. When an external assembly is resolved by the CLR, it consults the configuration files first to determine whether it needs to modify the process by which it resolves an assembly. As discussed, you can modify the resolution process to suit your needs.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset