Thursday, May 08, 2008

Loading Multiple Versions of same Assembly

Very Good and Unique Stuff – Copied from http://www.infosysblogs.com/microsoft/2007/04/loading_multiple_versions_of_s.html Thanks to Atul for sharing withh us.
The author of this article is: Atul Gupta

 

The other day a colleague asked if there was a way he could work with different versions of the same assembly in his code i.e. could he invoke say Method A on Version 1.0 and Method B on Version 2.0 of the same assembly?

I was initially surprised as to why he would want to do that? People ask about being able to automatically load newer version of the assembly with the application or in case of .net being able to load an assembly built with .net framework 1.1 in an application built with .net framework 2.0, but usually not this.

When he explained his situation it made sense. He was working with Sharepoint Server API and there have been some changes in the API between SPS 2003 and 2007. So there was a need to invoke some functionality using SPS 2003 assembly and some functionality using the newer version available with SPS 2007.

I can think of another case where you have programmed against say a buggy assembly and have written some workaround logic for the bug in your code. With the newer version of that assembly that bug has been fixed, but due to some constraints, you can't remove your workaround code and hence can't use the new version. There is however new functionality provided by this new version which you do want to use. Hence you would need to be able to call new methods on new version and the buggy method on the old version.

With this preamble, lets get into how to get this done. There are various permutations and combinations and I will address them. At a high level the solution is in working with one version as already programmed (typically using early binding) and with another version using reflection. Do also note that the assembly loading follows probing rules as defined in .NET SDK documentation. To keep things simple, I will keep the folder path simple and not get into such diversions.

Consider this extremely critical business logic inside of a uniquely named assembly - ClassLibrary1.dll as below. Again to keep things simple, I am not showing the various "using" statements with the code. Additionally to identify which version the method is called on, I return the version# in the string itself. I can do a dynamic query for assembly version and remove this hard coding, but the recall.. our moto is simplicity at this time and focus on loading different versions and not any other aspect, hence the hard coding.

    public class Class1
    {
        public static string Method1()
        {
            return "Method 1 called on version 1.0.0.0";
        }
    }

Then there is this Winform application that invokes this static Method1 and displays the returned string in message box. To help identify if this call has happened due to early binding or reflection, I prefix the string accordingly. I build the code with version 1.0.0.0 of ClassLibrary1.dll and run the application. On invoking the method, I see a message box displaying the appropriate version#. Before we get into multiple versions, a quick discussion of what happens with private as against strong named assemblies is in order.

Weakly named library assembly (privately deployed)

If you update the version of such an assembly and redeploy it with the application, the application is able to load it without any issues. The change in just minor version to 1.1.0.0 or major version also to 2.0.0.0 doens't impact and the application works fine. Note that this is in line with basics of versioning that aren't applicable to weakly named assemblies.

Strongly named library assembly (privately deployed)

If you update the version of such an assembly and redeploy it with the applicaton, the application will give a runtime error when it tries to invoke a method of the library assembly. Due to strong naming, the version policy comes into effect and the application tries to invoked method on version against which it was complied, in this case 1.0.0.0. When it doens't finds this at runtime, it will give an error. It won't automatically start using 1.1.0.0 version. For that you need to specify the
bindingRedirect in the application configuration file.

There are multiple ways to create this configuration file. One is to do this with Visual studio itself and when the application is compiled, the config file is also complied and deployed along wih it. However if you aren't sure of the exact syntax, you can use the Microsoft .NET Framework 2.0 Configuration MMC from Administrators group. Via this you can add the specific application for configuration, then configure the specific library assembly and specify the binding policy. The configuration file generated will look something like this

<?xml version="1.0"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="ClassLibrary1" publicKeyToken="fbc28d9ca2fc8db5" />
        <publisherPolicy apply="no" />
        <bindingRedirect oldVersion="1.0.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>


To reiterate, this bindingRedirect is required only for strongly named assemblies. Once this configuration setting is in place, the application is able to load the 1.1.0.0 assembly and invoke methods on it. The same logic will work for assemblies deployed in GAC.

Let us now get on with loading of different versions of the same assembly. See the sample code below on how to achieve it.

        private void btn_Click(object sender, EventArgs e)
        {
            string str = "early bind - ";
            str += ClassLibrary1.Class1.Method1();
            MessageBox.Show(str);
 
            Assembly al = Assembly.LoadFile(@"E:\Temp\old\ClassLibrary1.dll");
 
            Type t = al.GetType("ClassLibrary1.Class1");
            MethodInfo m = t.GetMethod("Method1");
            str = "reflection - " + (string)m.Invoke(null, null);
            MessageBox.Show(str);
        }

When we run the application, we expect the early binding call to go to the newer version and the call via reflection to go to the older version, that we have specifically deployed in another sub-folder. We are using Assembly.LoadFile method so that we can specify a path.

One important aspect to note here is that the application will have to be compiled with the newer version of the assembly if the assembly is strongly named. If that isn't done, as discussed earlier, we will have to use a bindingRedirect configuration setting. Such setting works for all assembly load calls and will even redirect the assembly load call that we are doing via reflection and hence we will not be able to invoke methods on older version. By explicitly building the application with newer version, we don't need to the bindingRedirect configuration setting and our reflection call to earlier version will then work.

Loading assemblies from GAC

There is slight difference in the way the assembly is loaded via reflection if working against GAC. To successfully load different versions from GAC it is best to use the AssemblyName class and specify the FullName of the assembly you want to load. As we all know, FullName includes name, version number, culture and public key token.

        private void btn_Click(object sender, EventArgs e)

        {

            string str = "early bind - ";

            str += ClassLibrary1.Class1.Method1();

            MessageBox.Show(str);

 

            AssemblyName asm = new AssemblyName("ClassLibrary1, Version=1.1.0.0, Culture=neutral, 
                                                                                            PublicKeyToken=fbc28d9ca2fc8db5"
);

            Assembly al = Assembly.Load(asm);

 

            Type t = al.GetType("ClassLibrary1.Class1");

            MethodInfo m = t.GetMethod("Method1");

            str = "reflection - " + (string)m.Invoke(null, null);

            MessageBox.Show(str);

        }

Needless to say that it also possible to load as many versions as you want using the option of reflection and you need not invoke any version via early binding. I have used to early binding option just to highlight a case where you are already working with a version and want to also specifically invoke methods on a different version. This also means that you can early bind to an older version and load the newer version by reflection.

While playing around with this, I also realized that one needs not worry about references added to the project. The compiler is intelligent enough to add them to the manifest only if a call is made to any method contained in the referenced assembly. If no call is made, the reference isn't included and hence the assembly won't be loaded at run time. You can easily verify this by viewing the manifest via ILDasm utility. 

 

No comments: