32

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.

Community
  • 1
  • 1
jayars
  • 1,347
  • 3
  • 13
  • 25
  • can you please post the StackTrace? – lokusking Mar 10 '17 at 10:07
  • You could try removing the `` section from your config (you shouldn't need it) and adding the `global` alias to your assemblies. Not sure if it will make a difference but I do this all the time without issues and i always have the global namespace added, like `"global,v10"`. – caesay Mar 10 '17 at 10:16
  • The reason the method isn't found is because it's probably trying to use the wrong assembly. Why is `loadData` and `Common.Data.UserDa..` highlighted red? that's a resharper error right? what does it say? – caesay Mar 10 '17 at 10:18
  • @caesay, yes, that's a resharper error `Cannot resolve symbol 'loadData'`, although VS builds successfully. The dlls have been renamed, that's why I added the `runtime` element in appconfig. Without it, I'll get a `FileNotFoundException - Could not load file or assembly 'Common.Data...` – jayars Mar 10 '17 at 10:23
  • Strange. Maybe you could try importing the DLL you call from Main under its normal name in the same folder as the executable, or compiling your own pair of DLLs to check if there isn't some issue in the libraries. – IS4 Mar 10 '17 at 10:43
  • @jayars - I'm looking at your binding redirect in the picture, and you are telling the runtime to redirect itself to v 2.0.0.0 - no matter what. You are cutting the 1.0.0.0 dll out of the picture. The first binding redirect should be changed to 0.0.0.0 - 1.0.0.0 => 1.0.0.0, instead of 0.0.0.0 - 2.0.0.0 => 2.0.0.0. I actually don't think you even need those 2 bindingRedirect tags. – N73k Apr 09 '19 at 21:10
  • @jayars - Some of your problems probably have to do with the fact that you can't rename a signed assembly. If you do, I think the assembly loader will say something like it can't find the assembly it's looking for. This may be why your stuff doesn't work but miguel's does. – N73k Apr 09 '19 at 23:00

3 Answers3

21

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("");
}
Jay Sullivan
  • 17,332
  • 11
  • 62
  • 86
Miguel Remirez
  • 363
  • 1
  • 11
  • I tried this (see Edit4), but still got the same `MissingMethodException`. I'm pretty sure just `` is enough (Edit3). I just don't know how to configure my csproj so it mimics the csc CLI compilation. – jayars Mar 14 '17 at 01:36
  • @jsjslim i see in your app.config code that the versions of BindingRedirect not are correctly, you need define a bindingRedirect as ´´ ´´ and when i said '4- Find the and put in False' i wanted to say set property < AutoGenerateBindingRedirects > to False – Miguel Remirez Mar 14 '17 at 08:32
  • 1
    thanks for edit. It was `false` that did the trick for me. Although both VS and Resharper complained, the build succeeded, and ran as expected. – jayars Mar 15 '17 at 23:51
  • 1
    for website the href should begin with "bin\" for instance – Cyclion Aug 03 '17 at 11:58
  • @miguel - But how do you add 2 references to the same-named dll (but different versions) in Visual Studio? When I try to do that (via "Add Reference" > "Browse"), I get "A reference to 'aaa.dll" could not be added. A reference to the component 'aaa' already exists in the project." – N73k Apr 09 '19 at 20:32
  • Is it possible to put the changes in the `web.config` file too? – user3748973 Sep 21 '22 at 06:10
1

Missing a complete answer which suits my needs i decided to donate my solution with the AssemblyResolve way to use two dlls and load the relevant from file, using reflection to resolve the type . For this demo i created two dlls called MathFuncs, trying to invoke their Add.add function will resolve in two different implementations.To see the different results switch the version integer between the values 1 and 2 :

public static int version = 1;
    public static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += Program.CurrentDomain_AssemblyResolve;
        version = 1;
        Type a = Type.GetType("MathFuncs.Add, MathFuncs, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
        MethodInfo methodInfo = a?.GetMethod("add");
        object result = null;
        if (methodInfo != null)
        {
            object[] parametersArray = new object[] {1, 2};
            result = methodInfo.Invoke(Activator.CreateInstance(a, null), parametersArray);
        }

        if (result != null)
        {
            Console.WriteLine((int)result);
        }
        else
        {
            Console.WriteLine("failed");
        }

        Console.Read();
    }


    public static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        FileInfo fi = null;
        if (version == 1)
        {
            fi = new FileInfo("C:\\Users\\ohbitton\\Desktop\\MathFuncs\\MathFuncs.dll");
        }
        else
        {
            fi = new FileInfo("C:\\Users\\ohbitton\\Desktop\\MathFuncs2\\MathFuncs.dll");
        }
        return Assembly.LoadFrom(fi.FullName);

    }

To get the complete name of the namespace for the Type.GetType you can use powershell :

([system.reflection.assembly]::loadfile("C:\Users\ohbitton\Desktop\MathFuncs\MathFuncs.dll")).FullName
Ohad Bitton
  • 460
  • 2
  • 14
0

Try to put your config details like as following.

 <dependentAssembly>
            <assemblyIdentity name="myAssembly"
                              publicKeyToken="32ab4ba45e0a69a1"
                              culture="neutral" />
            <bindingRedirect oldVersion="1.0.0.0"
                             newVersion="2.0.0.0"/>
  </dependentAssembly>

here you can provide you dll old version and new version. You can just follow this article and also this.

Hope this will help you.

Mukesh Kumar
  • 2,354
  • 4
  • 26
  • 37
  • Thanks for the reply. Unfortunately, it's still not working. I'm getting `FileNotFoundException` when using ``. Note in my post that the PublicKeyToken is identical, the the API has changed, and I'm trying to use both API at the same time, without using Reflection (to not lose intellisense and compile-time checks). I've updated my question to include more info. – jayars Mar 13 '17 at 01:12