Using 2 different versions of the same dll?

jayars picture jayars · Mar 10, 2017 · Viewed 24.8k times · Source

I have been given 2 pre-compiled dlls:

Common.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f3b12eb6de839f43, processorArchitecture=MSIL

Common.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=f3b12eb6de839f43, processorArchitecture=MSIL

Example of differences in API:

API

And I'm trying to load both into a single project to do something like this:

extern alias v10;
extern alias v20;

private static void UpgradeUser()
{
    // Load old user
    var userOld = new v10::Common.Data.UserData();
    userOld.loadData("user.dat");

    // Create new user
    var userNew = new v20::Common.Data.UserData();

    // Copy properties  
    userNew.FirstName = userOld._firstName;
    userNew.LastName = userOld._lastName;
    userNew.Age = userOld._age;

    // Invoke method from v10 and v20 API
    userOld.version();
    userNew.DisplayVersion();

    if (userNew.GetUserInfo() != userOld.getInfo())
    {
        throw new Exception("Discrepencies in upgrade ");
    }

    Console.WriteLine("Upgrade done!");
}

I've set up my project references and app.config to the following. I'm also manually copying the dlls into my output folder, matching the appconfig hrefs

<!-- [Edited: see history for previous version] -->
<Reference Include="Common.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f3b12eb6de839f43, processorArchitecture=MSIL">
  <HintPath>libs\Common.Data.1_0_0_0.dll</HintPath>
  <Aliases>v10</Aliases>
</Reference>

<Reference Include="Common.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=f3b12eb6de839f43, processorArchitecture=MSIL">
  <HintPath>libs\Common.Data.2_0_0_0.dll</HintPath>
  <Aliases>v20</Aliases>
</Reference>

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Common.Data" publicKeyToken="f3b12eb6de839f43" culture="neutral" />
      <codeBase version="1.0.0.0" href="libs\Common.Data.1_0_0_0.dll" />
      <codeBase version="2.0.0.0" href="libs\Common.Data.2_0_0_0.dll" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

I've gotten insofar as to build successfully.

But when I try to run it, I get a MissingMethodException for UserData.loadData.

Error

I've been through multiple stackoverflow posts, msdn and codeproject articles, but can't seem to get it to work.

Link 1, Link 2, Link 3, Link 4

Think I'm missing an important step but can't figure out what, and could really use some help.

[Edit1]

I've tried using the dlls separately, and they work. (Removed clutter. See version history for screenshots)

[Edit2]

I've tried Mukesh Kumar's proposal, and changed my app.config to:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Common.Data" 
                        publicKeyToken="f3b12eb6de839f43" 
                        culture="neutral" />
      <bindingRedirect oldVersion="1.0.0.0"
                       newVersion="2.0.0.0"/>
    </dependentAssembly>
  </assemblyBinding>
</runtime>

But it still does not work. I'm now getting FileNotFoundException.

[Edit3]

Ok, I'm almost certain the <bindingRedirect> is not correct, and should be <codeBase> instead.

I tried compiling from the CLI:

csc Program.cs /reference:v10=libs/Common.Data.1_0_0_0.dll /reference:v20=libs/Common.Data.2_0_0_0.dll

And it works fine. I was able to use both APIs at the same time:

enter image description here

But when I try to build it from Visual Studio, it complains about /reference, even though I've already specified the reference alias:

The extern alias 'v10' was not specified in a /reference option

I've tried modifying <Reference /> to include/exclude <SpecificVersion>True</SpecificVersion> with no effect.

I found this post, where the solution was to remove and re-add the paths. Using that solution, Visual Studio builds fine, but I'm back to System.MissingMethodException.

Feels like I've almost got it, but not quite. How can I get Visual Studio to build correctly?

[Edit4]

I've tried miguel's method, but still not working.

In this attempt, I've renamed the dlls back to their original names, and store them in different folders instead.

enter image description here

I then updated app.config to do a bindingRedirect as well as codebase.

When running it, I'm getting MissingMethodException.

I wasn't sure what 4- Find the and put in False meant, so I tried all combinations of <Private>, <SpecificVersion>, as well as setting <Reference Include> to FQNs, but none of those combinations work.

Looking at my 3rd edit, I've managed to compile and run the sample successfully (with my renamed dlls + app.config codebase hrefs) when done via the CLI.

My problem now is, how do I configure my csproj, so it builds the same.

Answer

Miguel Remirez picture Miguel Remirez · Mar 13, 2017

You need to use a dependentAssembly with bindingRedirect but also you need put dlls in different folder or save with a different name. With this done, you need to put the following in your app.config:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
        <assemblyIdentity name="myAssembly"
                          publicKeyToken="here token dll"
                          culture="neutral" />
       <bindingRedirect oldVersion="0.0.0.0-1.0.0.0" newVersion="1.0.0.0" />
       <bindingRedirect oldVersion="1.0.0.1-2.0.0.0" newVersion="2.0.0.0" />
       <codeBase version="1.0.0.0" href="folder\namedll.dll" />
       <codeBase version="2.0.0.0" href="folder\namedll.dll" />
      </dependentAssembly>
   </assemblyBinding>

With this code should compile and run, but sometimes VS deletes or overwrite the code in the app.config when compiles it. You need to check it in config file of compilation folder. If this succeeds, you can edit the .csproj. For this you must do:

1- Unload the project affected
2- Right click on project
3- Click Edit project
4- Find the <AutoGenerateBindingRedirects> property and set it to False
5- Save changes and reload project

This works for me. In my project, I'm using two versions of Automapper.

Finally, another solution is to use the AppDomain.CurrentDomain.AssemblyResolve build event and load the specific dll.

For that, you need catch the event:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);

public static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    //debug and check the name
    if (args.Name == "MyDllName")
        return Assembly.LoadFrom("c:\\pathdll\midllv1.dll")
    else if(args.Name ="MyDllName2")
        return Assembly.LoadFrom("c:\\pathdll\midllv2.dll");
    else
        return Assembly.LoadFrom("");
}