63

I've been tasked in creating a new module for an application, and so, I'm adding new DLLs to the project. This is all fine and well.

However, in my DLLs I'd like to use a new version of an external DLL (over which I have no control). If I just reference the new DLL and work with that one only, my code will work, but the old code will stop functioning.

Could not load file or assembly 'itextsharp, Version=5.0.6.0, Culture=neutral,
PublicKeyToken=8354ae6d2174ddca' or one of its dependencies. The located assembly's
manifest definition does not match the assembly reference. (Exception from HRESULT:
0x80131040)

I've tried a simple trick of changing the DLLs name, but that apparently was a bit too naive of me, to think it would work. I've tried using the external aliases (by defining them in my references), but I still don't know how to get two files with the same name into one BIN folder...

What should I do?

MBender
  • 5,395
  • 1
  • 42
  • 69

6 Answers6

63

Let's assume you have a project structure as follows:

Project Diagram

...where A and B are class libraries, and C is an executable-type project (such as a unit test or console project).

Let's assume the folder structure is like this:

ABC.sln
A/A.csproj
A/...
B/B.csproj
B/...
C/C.csproj
C/...
lib/thirdparty4/thirdparty.dll
lib/thirdparty5/thirdparty.dll

If we attempted to naively reference our projects together, we'd have a problem: two versions of thirdparty.dll will be copied into the same folder (the output (i.e., bin) directory of C). We need a way for C to copy both dlls into its output directory, and provide a mechanism for referencing either one.

To solve this, I modified C.csproj to contain the following:

<ItemGroup>
  <Content Include="..\lib\thirdparty4\thirdparty.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <Link>thirdparty4\thirdparty.dll</Link>
  </Content>
  <Content Include="..\lib\thirdparty5\thirdparty.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <Link>thirdparty5\thirdparty.dll</Link>
  </Content>
</ItemGroup>

This will instruct it to create both thirdparty4\thirdparty.dll and thirdparty5\thirdparty.dll in its output directory.

Now, after building C, its output directory looks like this:

C\bin\Debug\A.dll
C\bin\Debug\B.dll
C\bin\Debug\C.dll
C\bin\Debug\thirdparty4\thirdparty.dll
C\bin\Debug\thirdparty5\thirdparty.dll

To instruct C to use both of these dlls, I added an App.config file to it, with the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="thirdparty" culture="neutral" publicKeyToken="1234567890123445"/>
        <bindingRedirect oldVersion="4.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
        <codeBase version="4.0.0.0" href="thirdparty4\thirdparty.dll" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="thirdparty" culture="neutral" publicKeyToken="1234567890123445"/>
        <bindingRedirect oldVersion="5.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
        <codeBase version="5.0.0.0" href="thirdparty5\thirdparty.dll" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

This will instruct the assembly to, depending on which version is in need, use one DLL or the other, both of which will be available within subfolders of the output directory. (The bindingRedirect elements are optional, but you can use them if you need a range of revisions for this to apply to.)

Jay Sullivan
  • 17,332
  • 11
  • 62
  • 86
  • based on your proposal, it is possible to choose on runtime which assembly to load?; I'm having 3 different DLLs with mostly the same methods and so on, but inner working is different, so I'd like to be able to pick a version and use it during the whole process. – coloboxp Jan 27 '19 at 19:48
  • @coloboxp If the assemblies are known at compile time and can all be referenced then one can use `extern alias` and facades. – user2864740 Jun 03 '19 at 06:52
  • 2
    Nice information: the assemblies can also be put in the same output directory with different names IF they are strongly named (I have, for example, `Newtonsoft.Json.dll` and `Newtonsoft.Json.12.dll`.) – user2864740 Jun 03 '19 at 06:53
  • Hi Jay, My thirdparty dll is Microsoft.Azure.DocumentDB.Core.dll. I have the same project structure: proj A references v2.1.3 of the dll (which has bulkimport capabilities) and proj B references v2.5.1 (which hasn't bulkimport cap. but some other features). I can't get your stuff working and get "Method not found" when invoking method in proj A. Do you have any idea? thanks. – kevinob Sep 27 '19 at 12:48
  • Unable to achieve this. Any one can guide me on this sample: https://drive.google.com/open?id=1-nvKu5UNPwk7zwxETkHv4bB8qJK6HeTx – user203687 Dec 17 '19 at 16:37
  • I did everything that you mention @Jay, but keep getting an error "Could not load file or assembly 'itextsharp_5_2' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. . The assembly reference did not match the assembly definition found." – Ulana Dec 19 '19 at 20:06
  • 2
    Just FYI for anyone who ends up here: Doing this in the Web.Config on an old ASP.NET Web Form project, I had to set the href property on the tag to "bin\thirdparty5\thirdparty.dll" for it to find the DLL – zfrank Apr 23 '21 at 21:08
  • does anyone know why mine is not working? https://stackoverflow.com/questions/76330747/multi-version-dll-use-on-the-same-c-sharp-application – Jami May 25 '23 at 12:02
25

You can load another version into a specific AppDomain

Possibly too detailed, but here is an article that demonstrates the use of AppDomains in a useful setting and how they work:

http://msdn.microsoft.com/en-us/magazine/cc164072.aspx

In a very basic sense it comes down to this sample code:

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

    static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        if (/*some condition*/)
            return Assembly.LoadFrom("DifferentDllFolder\\differentVersion.dll");
        else
            return Assembly.LoadFrom("");
    }
MBender
  • 5,395
  • 1
  • 42
  • 69
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 2
    Not as pretty as I had hoped, but it got the job done. Thanks! – MBender May 06 '11 at 21:31
  • 2
    I've allowed myself to edit your answer to include a code sample which I used to resolve the issue. But the main reason is that the second link is long dead. Could you, by chance, link a similar resource to the old one? – MBender Sep 08 '15 at 13:19
  • 1
    @Shaamaan I have no way of finding what the original link referred to :( Here's a good entry page that could be a good replacement: https://msdn.microsoft.com/en-us/library/ms172158(v=vs.110).aspx – sehe Sep 08 '15 at 13:47
  • @Shaamaan . I assume your DLL had a dependency on other dll (with different version). So you added the above code to resolve the issue. Is that Right ? In your DLL's source where/when did you execute the above code to hook into ResolveEventHandler(). This should happen in DLL initialization Right ? Did you use the approach of PreApplicationStartMethod() for that ? I as I could not get the PreApplicationStartMethod() approach working. – Feru Jan 18 '18 at 19:44
  • @Feru Please bear in mind the question was asked over 5 years ago, so my memory of the exact use-case is a bit fuzzy at this time. Generally the app I was working on had "modules" (think main screen with big buttons). We were tasked in creating a new module, and so we decided to keep it as a separate library. And then our library used a never version of a shared library (I think it was PDF-related). I think the resolve code was in the main app, but due to the modular nature it could be hooked up to the button press, I think. Don't quote me on this - again, it was a quite a while ago. – MBender Jan 19 '18 at 08:36
  • @Feru Recently I was doing something similar. My goal was to force the app to use a different version of a hooked up DLL (shared interfaces). Basically, I was re-using the MS CRM Deployment Tool app code for our own purposes. I think if you get the MS CRM SDK for an older CRM (I think 2011 should be OK), it came with the source code for the deployment tool. I'd suggest you look through it. (The latest MS CRM SDK definitely doesn't have that source code, do remember that!) – MBender Jan 19 '18 at 08:40
  • I don't think this answer really has to do with AppDomains. There's only 1 AppDomain going on here. It's about programmatically finding/loading dlls. – N73k Apr 12 '19 at 16:05
  • @N73k The answer certainly does. If you mean you'd want to add more detailed sample code, you're quite welcome to – sehe Apr 12 '19 at 17:52
  • @sehe - Then where is the 2nd AppDomain? There isn't one. – N73k Apr 12 '19 at 17:59
  • That's what I said. Would it be okay for you if I removed the related sample code? (The related sample was to widen the discussion a little bit: you can conceivable remove the problem of conflicting versions if you change the resolution. Manifest files or SxS policies have similar capabilities) – sehe Apr 12 '19 at 19:47
13

If the AppDomains solution isn't applicable in your case, you are under time pressure, have conflicting requirements (like that ever happens), and don't mind ridiculously contrived hacks:

  • Decompile the newer version of the assembly using the ildasm tool (part of the Developer Command Prompt included with Visual Studio)
  • Edit the generated .il file to find/replace the assembly namespace references. Using the cited example, this would be a change from itextsharp.X to itextsharp.new.X
  • Likewise, edit the value for the AssemblyTitleAttribute. This requires translating the ASCII characters to hex.
  • Recompile the .il file using ilasm
  • Note that this may need to be repeated for any dependent assemblies (e.g. - someassembly.core.whatever)
  • Add the new .dlls to your project with a different name and reference them explicitly (rather than via nuget or whatever)

Hey, don't look at me like that. I did say ridiculously contrived hack ...

Vecht
  • 131
  • 1
  • 2
6

Another viable option is to use extern, as described here:

http://blogs.msdn.com/b/abhinaba/archive/2005/11/30/498278.aspx

fozylet
  • 1,279
  • 1
  • 13
  • 25
1

You can also rely on assembly binding redirection for your strong named assembly as described in http://msdn.microsoft.com/en-us/library/2fc472t2.aspx.

You would only have one version of the file (the latest), and both references would resolve to it.

fsimonazzi
  • 2,975
  • 15
  • 13
0

If someone is interested in how to achieve this on a ASP.NET-MVC project, then read on:

I had a requirement to switching a DB connector of a particular version during runtime based on a appsetting key (coming from web.config) so this is how I went about it.

In your Global.asax file, under the Application_Start method, I simply added the AssemblyResolve event handler to AppDomain.CurrentDomain:

using System;
using System.Reflection;
using System.IO;
using System.Configuration;

protected void Application_Start()
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolveForMyConnector);

    //rest code omitted for brevity 
}

Then I created a method that would do the switching at runtime based on my configuration setting. In this I placed my new version of the dll file under NewVesion folder and the original version of the dll resides in the bin folder:

static Assembly CurrentDomain_AssemblyResolveForMyConnector(object sender, ResolveEventArgs args)
{
    bool AssemblySwitch = Convert.ToBoolean(ConfigurationManager.AppSettings["AssemblySwitch"]);
    if (AssemblySwitch)
    {
        //Get custom path where new version of dll resides
        string appPath = AppDomain.CurrentDomain.BaseDirectory;
        string latestdllpath = Path.Combine(appPath, "NewVersion");
        return Assembly.LoadFrom(latestdllpath+"\\mynewversion.dll");
    }
    else
    {
        //Get bin path of the project
        var binpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath ?? "");
        return Assembly.LoadFrom(binpath+"\\myoriginalversion.dll");
    }
}

Full Global.asax file:

using System;
using System.Reflection;
using System.IO;
using System.Configuration;

namespace MyProject
{
    public class MvcApplication : System.Web.HttpApplication
    {
        static Assembly CurrentDomain_AssemblyResolveForMyConnector(object sender, ResolveEventArgs args)
        {
            bool AssemblySwitch = Convert.ToBoolean(ConfigurationManager.AppSettings["AssemblySwitch"]);
            if (AssemblySwitch)
            {
                //Get custom path where new version of dll resides
                string appPath = AppDomain.CurrentDomain.BaseDirectory;
                string latestdllpath = Path.Combine(appPath, "NewVersion");
                return Assembly.LoadFrom(latestdllpath + "\\mynewversion.dll");
            }
            else
            {
                //Get bin path of the project
                var binpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath ?? "");
                return Assembly.LoadFrom(binpath + "\\myoriginalversion.dll");
            }
        }
        protected void Application_Start()
        {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolveForMyConnector);
        }
    }
}
Rahul Sharma
  • 7,768
  • 2
  • 28
  • 54