Binding Policy in .NET

So you're ready to deploy version 1.1 of your library, and you'd like it to
replace version 1.0 for existing applications. Or perhaps something else has
globally upgraded to 1.1, and you need to downgrade it for a particular
application where 1.1 is causing problems. Handling these issues for .NET
applications is the job of runtime binding policy. In this article, I'll
explain the basics of runtime binding policy, and show you how you can
customize the process for your own applications.

.NET applications use two different types of assemblies: private
assemblies and shared assemblies. Private assemblies are identified
by their name and are deployed for the use of only a single application. Shared
assemblies are identified by a strong name (a type of digital identity
that includes the name of the assembly, its version number, its culture
identity, and a public key token).

When you build an assembly, information about all other assemblies that it
refers to is stored in the assembly manifest. The manifest, however, does not
store the exact path of the assembly because this path may might differ on the
computer where the assembly is deployed. At runtime, when a class is referenced,
the Common Language Runtime (CLR) reads the assembly manifest, retrieves the
identification information for the referenced assembly, and then attempts to
locate the referenced assembly. The mechanism used by the CLR to locate a
private assembly is different from that used for a shared assembly. It's
easy for the CLR to tell the difference, because public key tokens are only
stored in the manifest for shared assemblies.

This particular example adds the bin\path1 and
bin\path2 folders to the list that the CLR checks for private
assemblies.

The next step depends on whether the referenced assembly is for a particular
culture. Either way, the CLR checks a list of locations for the assembly, but
the list differs. If there is no culture information, then the search order is
as follows:

ApplicationBase\AssemblyName.dll

ApplicationBase\AssemblyName\AssemblyName.dll

ApplicationBase\PrivatePath1\AssemblyName.dll

ApplicationBase\PrivatePath1\AssemblyName\AssemblyName.dll

ApplicationBase\PrivatePath2\AssemblyName.dll

ApplicationBase\PrivatePath2\AssemblyName\AssemblyName.dll

]

ApplicationBase\AssemblyName.exe

ApplicationBase\AssemblyName\AssemblyName.exe

ApplicationBase\PrivatePath1\AssemblyName.exe

ApplicationBase\PrivatePath1\AssemblyName\AssemblyName.exe

ApplicationBase\PrivatePath2\AssemblyName.exe

ApplicationBase\PrivatePath2\AssemblyName\AssemblyName.exe

If there is culture information for the target assembly, then the search list is
a bit different:

ApplicationBase\Culture\AssemblyName.dll

ApplicationBase\Culture\AssemblyName\AssemblyName.dll

ApplicationBase\PrivatePath1\Culture\AssemblyName.dll

ApplicationBase\PrivatePath1\Culture\AssemblyName\AssemblyName.dll

ApplicationBase\PrivatePath2\Culture\AssemblyName.dll

ApplicationBase\PrivatePath2\Culture\AssemblyName\AssemblyName.dll

]

ApplicationBase\Culture\AssemblyName.exe

ApplicationBase\Culture\AssemblyName\AssemblyName.exe

ApplicationBase\PrivatePath1\Culture\AssemblyName.exe

ApplicationBase\PrivatePath1\Culture\AssemblyName\AssemblyName.exe

ApplicationBase\PrivatePath2\Culture\AssemblyName.exe

ApplicationBase\PrivatePath2\Culture\AssemblyName\AssemblyName.exe

Here, ApplicationBase is the directory in which the requesting
application is installed, AssemblyName is the name of the assembly to
locate, Culture is the culture code for the target assembly, and
PrivatePath1 and PrivatePath2 are the hints provided in the
<probing> element of the application configuration file. Of course, if there are
more than two hints, each one is searched in turn. As soon as the CLR finds a
matching assembly, it binds to the assembly and stops searching.

If none of these locations yields a copy of the assembly, then the binding
process fails and your application won't run.

Binding Policy for Shared Assemblies

Here are the steps that the CLR takes when you call code from a shared
assembly:

The CLR determines the correct version of the assembly to load by examining the
applicable configuration files. I'll explain this process in the next section of
this article.

The CLR checks to see whether the requested assembly has already been
loaded. If it has, then the CLR binds to the loaded copy and stops
searching.

The CLR then checks for the requested assembly in the Global Assembly
Cache (GAC). If the assembly is in the GAC, then the CLR uses that copy
and stops searching.

The CLR next looks for a <codebase> element in the
application's configuration file. If one is present, it checks that path for the
assembly, loading it if found.

If the requested assembly hasn't been located yet, the CLR proceeds to
search for it as if it were a private assembly, following the rules from the
previous section.

Determining the Proper Version

Here's where it gets fun. Remember that I started the article by discussing
scenarios in which you might like to customize the binding process? You can do
this with a series of configuration files that allow you to tell an application
to use a particular version of a shared assembly, even if it was compiled with a
different version. These binding policies are applied at three
levels:

Application Policy Resolution

Publisher Policy Resolution

Administrator Policy Resolution

In the application policy resolution stage, the CLR checks for a
<bindingRedirect> tag in the application's configuration
file, similar to this example:

This file tells the CLR to load version 1.1.0.0 of MyCalledAssembly, even
though the application was compiled to use version 1.0.0.0.

In the publisher policy resolution stage, the CLR checks for a policy file
distributed with the assembly itself. A publisher policy file starts as an XML
file, very similar to an application configuration file:

This particular publisher policy file tells the CLR to use version 1.1.0.5 of
the assembly to satisfy requests for version 1.0.0.0. Before a publisher policy
file can take effect, it must be compiled, using the al.exe tool:

The compiled publisher policy file can then be installed in the GAC along
with the assembly to which it refers. Publisher policy files generally override
application policy. However, you can force your application to ignore a
publisher policy file by adding <publisherPolicy apply="no"/>
to your application configuration file.

The final stage in applying policy rules is administrator policy resolution.
The administrator of a computer can specify system-wide binding rules in the
machine.config file. These rules override both application policy and publisher
policy. These machine.config settings use the same format as the binding policy
settings in the application configuration file.

GUI to the Rescue

You can configure both application binding policy and administrator binding
policy without editing XML files by hand. These are among the tasks that the
.NET Framework Configuration tool (which you can launch from Start-Programs-
Administrative Tools) can perform. If you launch this tool and expand the
tree, as shown in FIgure 1, you'll find two places where you can work with
configured assemblies.

Figure 1: Working with configured assemblies in the Microsoft .NET Framework Configuration Tool

A configured assembly is simply one that has an explicit binding
policy. The Configured Assemblies node directly beneath My Computer lets you set
administrator policy for configured assemblies. The other nodes, beneath
specific applications, let you set application policy for configured assemblies.
To work with a specific application in this tool, click on the Applications
node, select "Add an Application to Configure," and follow the instructions to
add your application to the tree.

Whether you're working at the application or the administrator level, the
procedures here are the same. Click on one of the Configured Assemblies nodes,
and you'll have two choices. The first choice allows you to add a new configured
assembly to the list for the application or computer. The second allows you to
view a list of all configured assemblies. From the list, you can double-click an
assembly to set its properties. Figure 2 shows the properties dialog box.

Figure 2: Setting policy properties for a configured assembly

The properties dialog box has three tabs:

The General tab shows the basic identifying information for the assembly and
(for an application policy) allows you to override any publisher policy.

The Binding Policy tab lets you specify the mapping between the version that
an application requests and the version that the CLR actually delivers.

The Codebases tab lets you tell the CLR where to find particular versions of
the assembly.

Rules of Thumb

As with some other areas of the .NET Framework, binding resolution offers an
overwhelming number of options. I'll leave you with some thoughts on ways in
which you might choose to use these options.

If you don't have any compelling reason to get into the process, don't. Just
messing around for the sake of messing around won't help you out.

Publisher policy files are for publishers. The best time to use them is when
you're issuing a service pack for a component, and want to make sure that people
get the benefits of your fixes. But remember, the application developer can
still override your policy file.

On the other hand, as an application developer, don't override publisher
policy unless you've got a specific reason.

The prime reason for an application-level binding policy is to provide
upgrades for an application that's already deployed. When a new version of a
shared component comes out that your application can benefit from, an
application policy file will allow you to provide these benefits without
recompiling or replacing existing installations.

Finally, as an administrator, you may make rare use of an administrator
policy file to help keep a machine in a known good state. If there's a shared
network component with a bug, for example, you can use an administrator policy
file to make sure that the bug-fixed version is uniformly used by all of the
.NET applications on the box.

Mike Gunderloy
is the lead developer for Larkware and author of numerous books and articles on programming topics.