Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build/deploy project that requires multiple versions of the same assembly?

I am working on a project that uses conflict.dll version 6.2, but the project also uses helper.dll that uses conflict.dll version 5.8.

I could install 6.2 and 5.8 into the GAC, but I'ld like to have this project xcopy deployable. I believe .net will search for the assemblies in the application bin directory like so: \bin\conflict.dll (6.2) \bin\5.8\conflict.dll (5.8)

But at this point, how do I add a reference to both versions of conflict.dll in the project, and then how do I make sure the old conflict.dll deploys to \bin\5.8? Do I create a build action or is there another way?

Thanks

like image 209
djmc Avatar asked Sep 05 '10 19:09

djmc


People also ask

How do I use two versions of the same DLL in the same project?

config. Under <runtime> , add a <codeBase> tag for each version of the DLL. This will resolve the runtime assembly loading conflict. That's it, now we can use both versions as we please.

What is Side by Side versioning?

Side-by-side execution is the ability to run multiple versions of an application or component on the same computer. You can have multiple versions of the common language runtime, and multiple versions of applications and components that use a version of the runtime, on the same computer at the same time.


3 Answers

After many hours of searching and cursing, I found a solution that works and is easy and reliable to implement.

The problem like all the other answers have pointed out is that all of the following must be satisfied:

  1. Both versions of the DLL must have the same name, otherwise the runtime will complain that the name doesn't match the manifest.
  2. The runtime has to be able to find both assemblies in the search path.
  3. A version redirect is not possible because of breaking changes.
  4. AppDomain.ResolveAssembly never gets called in this example because the assembly has been loaded once already.

The solution is the following, in steps:

  1. Create a directory in your solution directory such as lib\ with this hierarchy:

    lib\Conflict\v1\Conflict.dll
    lib\Conflict\v2\Conflict.dll

  2. Add the following to your app/web.config:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Conflict" publicKeyToken="111111111111" />
      <codeBase version="1.0.0.0" href="bin\Conflict\v1\Conflict.dll" />
      <codeBase version="2.0.0.0" href="bin\Conflict\v2\Conflict.dll" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>
  1. Add a post-build event with an xcopy:

    xcopy $(SolutionDir)\lib $(TargetDir) /Y /S

  2. Build once so that the files are copied. Click on "Project -> Show all files". Right click on bin\Conflict and do Include in Project (saves you from doing it in code). This is necessary to have the files deployed if you package a web application.

Done!

like image 111
georgiosd Avatar answered Nov 07 '22 12:11

georgiosd


I believe .net will search for the assemblies in the application bin directory like so: \bin\conflict.dll (6.2) \bin\5.8\conflict.dll (5.8)

No, this is wrong. I would suggest you reading this article to learn more about what heuristics does the CLR use for probing.

This being said, you cannot have two different versions of the same assembly loaded in the same application domain you can load different versions of the same assembly into the same application domain but it is considered bad practice and should be avoided. In your case this means that you will have to choose which version of the conflicting assembly you want to use. You have a couple of choices:

  1. Recompile helper.dll to use the latest version of conflict.dll (I will definetely go with this one if Ihave the source code for helper.dll).
  2. Choose which version of conflict.dll you want and apply a <bindingRedirect> in your config file. For example if you want to use the latest version:

    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="conflict"
                                  publicKeyToken="xxxxxxxxx"
                                  culture="neutral" />
                <bindingRedirect oldVersion="5.8.0.0" 
                                 newVersion="6.2.0.0" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
    

This will effectively instruct the CLR to load version 6.2 of conflict.dll when it tries to resolve references for helper.dll. Note that if the two versions are strongly signed with different keys this technique won't work. Obviously as helper.dll has been compiled against version 5.8 if you have any differences (missing methods, different method signatures) you will get a runtime exception when trying to call a conflicting method so do this only if you are absolutely sure what you are doing.

Conclusion: no matter which path you decide to take, you will have to xcopy in the bin folder a single version of the conflict.dll.

like image 25
Darin Dimitrov Avatar answered Nov 07 '22 14:11

Darin Dimitrov


In support of Darin's answer of course you need to be eliminating such multi-version problems. His solution of using a binding redirect is a good one - +1 there. I can offer a solution that'll allow you to keep both if absolutely necessary, but you'll have to write a bit of code.

The only real problem you have here is that the two deployed filenames would have to be the same in order to be picked up by default by the loader. You could cheat really horribly and simply deploy the 5.8 dll as Conflict.exe so it could sit side by side Conflict.dll (and newer) and you'd find that it works.

Also, following the links through from Darin's answer you come to this topic MSDN topic on probing. Based on the content of this, you could simply deploy the 5.8 dll into bin\Content\Content.dll and when the runtime searches for it, it will look in this subfolder automatically.

However - that isn't a good solution :)

EDIT - NEW SOLUTION

If both versions of Conflict.dll are already signed, have you actually tried deploying one of the versions with a slightly different name? I've just set up a winforms app with two assembly references to different versions of the same (signed) assembly. This causes a couple of problems with the build, because the last-referenced version will be deployed to the bin folder, and the other one will not (so you have to manually copy in both; renaming one of them accordingly). Then I try running the app, which displays a message box containing two constant strings; one from each version of the assembly. It works absolutely fine.

Download a demo of this here - don't build it (otherwise you have to do the file renaming); just open the forms app's bin\debug folder and run the exe.

ClassLibrary1.dll and ClassLibary1vanything.dll are v1.0.0.0 and v2.0.0.0 of an assembly with otherwise the same name and public key. Despite the fact that classlibrary1vanything.dll has the wrong filename, it still works (probably because it is signed).

In the app.config I did put in a codebase hint, and thought that was why it worked (originally I deployed it as a different filename), but then I commented it out and it still worked. The codebase is probably most useful instead when the assembly has to be deployed to a sub folder or completely different location.

ORIGINAL TEXT

I've tried to get the second of the options mentioned in this support article from MS to work, but it doesn't seem to want to.

There is no doubt some clever way to do it out of the box, but since I'm not clever enough to have found it (yet), I would instead embellish and use the third of the options displayed in the aforementioned support topic and hook into the AssemblyResolve event of the app domain.

If you add your own configuration (probably just in appSettings really) for the full name of the assembly to be bound to a different filename, then in your AssemblyResolve event handler you can consult the name of the assembly that is to be loaded to see if it's in your configuration. If it is, grab hold of the location and use Assembly.LoadFrom to load it.

Thus, once you have something like this in place, you simply add an entry for the Conflict v5.8 assembly name in there along with the filename that the app should use.

I don't know what type of app it is that you're deploying but in win forms, console apps and services AppDomain.CurrentDomain.BaseDirectory will be equal to the bin folder and you can join that with the filename you want to load. Websites are a little trickier.

Should work a treat.

like image 1
Andras Zoltan Avatar answered Nov 07 '22 13:11

Andras Zoltan