58

I develop a library with some functional named CompanyName.SDK which must be integrated in company project CompanyName.SomeSolution

CompanyName.SDK.dll must be deployed via NuGet package. And CompanyName.SDK package has a dependency on 3rd party NuGet packages. For good example, let's take Unity. Current dependency is on v3.5.1405-prerelease of Unity.

CompanyName.SomeSolution.Project1 depends on Unity v2.1.505.2. CompanyName.SomeSolution.Project2 depends on Unity v3.0.1304.1.

Integrating CompanyName.SDK into this solution adds dependency on Unity v3.5.1405-prerelease. Let's take that CompanyName.SomeSolution has one runnable output project CompanyName.SomeSolution.Application that depends on two above and on CompanyName.SDK

And here problems begin. All Unity assemblies has equal names in all packages without version specifier. And in the target folder it will be only one version of Unity assemblies: v3.5.1405-prerelease via bindingRedirect in app.config.

How can code in Project1, Project2 and SDK use exactly needed versions of dependent packages they were coded, compiled and tested with?

NOTE1: Unity is just an example, real situation is 10 times worse with 3rdparty modules dependent on another 3rdparty modules which in turn has 3-4 versions simultaneously.

NOTE2: I cannot upgrade all packages to their latest versions because there are packages that have dependency not-on-latest-version of another packages.

NOTE3: Suppose dependent packages has breaking changes between versions. It is the real problem why I'm asking this question.

NOTE4: I know about question about conflicts between different versions of the same dependent assembly but answers there does not solve the root of a problem - they just hide it.

NOTE5: Where the hell is that promised "DLL Hell" problem solution? It is just reappearing from another position.

NOTE6: If you think that using GAC is somehow an option then write step-by-step guide please or give me some link.

Community
  • 1
  • 1
v.karbovnichy
  • 3,183
  • 2
  • 36
  • 47
  • 5) DLL hell was the problem that you couldn't have two different versions of the same DLL on the *system*. This has been solved. That doesn't mean that *all* problems with DLLs have been solved :D Dependencies are still tricky, and they will always be tricky, really. Are the versions backwards compatible? You could use assembly binding redirection if that's the case. – Luaan Sep 23 '15 at 12:32
  • @Luaan see NOTE3. When it's not working with bindingRedirects, I must ask another developers to update old packages in their project but it is not-a-solution. They just have no time and budget for that. – v.karbovnichy Sep 23 '15 at 12:35
  • 1
    NOTE2 is the killer. If you cannot solve that then I can only suggest that Project1, Project2 and SDK are deployed to their own runtime directories, so they can have their version specific 3rd party dependencies. – Polyfun Sep 23 '15 at 12:39
  • Yes, breaking changes are always a problem. There is no real solution - you have an incompatible dependency chain. It *is* possible to load different versions of the same assembly in the same process and the same `AppDomain`, but it's a very ugly hack, and it breaks in subtle ways. If you can separate your code into different processes or at least AppDomains, this can get a lot easier - you'd just need to maintain a couple of interfacing libraries. – Luaan Sep 23 '15 at 12:39
  • Just a quick thought, would adding a codebase element for each individual version of unity work? Like this: http://stackoverflow.com/questions/638310/how-to-operate-with-multiple-assembly-versions-in-private-folders-using-config – Vincent Sep 28 '15 at 07:12
  • @Vincent, in inis case 1) I must place each assembly in its own folder by somehow changed buildprocess and 2) after that I must make sure that code from different projects load corresponding assemblies. Will try. – v.karbovnichy Sep 28 '15 at 07:42
  • @all guys, what do you think about [this technique](http://blogs.msdn.com/b/abhinaba/archive/2005/11/30/498278.aspx)? – v.karbovnichy Sep 29 '15 at 14:19
  • And another info about [loading multiple versions of the same assembly](https://msdn.microsoft.com/en-us/library/dd153782(v=vs.110).aspx#avoid_loading_multiple_versions) – v.karbovnichy Sep 29 '15 at 14:42
  • @kpa6uk workarounds only treat the symptoms not cure the illness. You must reconsider architecture of your system and package management. Maybe the technique you gave a link will work. But if the process of using your package by `NuGet` will be burdensome I assure you that will cause decreasing quality of your code base. Unfortunately, I would consider introducing `NuGet` harmful into company code base where there is no clear architecture and nobody have eye on creating packages and actually don't know how to proper structure packages to avoid common issues. – Arkadiusz K Sep 29 '15 at 19:33
  • somehow I think this question remained un-answered actually. I'd be interested to see if it's possible to exclude Unity from getting packaged into `CompanyName.SDK` and when it's bundled into `SomeSolution` it could use whichever version of `Unity` is referenced by the solution or bring down its originally preferred version if solution does not already require `Unity` – Mobigital Feb 21 '17 at 23:41
  • @Mobigital actually yes, all answers are not-so-good. I unmarked question as answered. – v.karbovnichy Feb 22 '17 at 06:29
  • It looks that `...` section in nuspec file might do the trick, I've seen packages created with Nuget Package Explorer contain these dependencies listed out and not included into the package content, which should allow final project to pull all packages directly from their sources (latest or what not) and perhaps with use of `SpecificVersion=false` and some `bindings` would allow to mingle best fitting dependencies in one project. ? – Mobigital Feb 22 '17 at 17:42
  • Also there is an attribute `developmentDependency="true"` that goes in specific package element in `packages.config` which might be useful but I haven't experimented with it. I am hoping for `` element to do more good for this purpose. – Mobigital Feb 22 '17 at 17:46
  • (correction by `bindings` above I meant `bindings redirect`) – Mobigital Feb 22 '17 at 18:01

2 Answers2

14

Unity package isn't a good example because you should use it only in one place called Composition Root. And Composition Root should be as close as it can be to application entry point. In your example it is CompanyName.SomeSolution.Application

Apart from that, where I work now, exactly the same problem appears. And what I see, the problem is often introduced by cross-cutting concerns like logging. The solution you can apply is to convert your third-party dependencies to first-party dependencies. You can do that by introducing abstractions for that concepts. Actually, doing this have other benefits like:

  • more maintainable code
  • better testability
  • get rid of unwanted dependency (every client of CompanyName.SDK really needs the Unity dependency?)

So, let's take for an example imaginary .NET Logging library:

CompanyName.SDK.dll depends on .NET Logging 3.0
CompanyName.SomeSolution.Project1 depends on .NET Logging 2.0
CompanyName.SomeSolution.Project2 depends on .NET Logging 1.0

There are breaking changes between versions of .NET Logging.

You can create your own first-party dependency by introducing ILogger interface:

public interface ILogger
{
    void LogWarning();
    void LogError();
    void LogInfo();
} 

CompanyName.SomeSolution.Project1 and CompanyName.SomeSolution.Project2 should use ILogger interface. They are dependent on ILogger interface first-party dependency. Now you keep that .NET Logging library behind one place and it's easy to perform update because you have to do it in one place. Also breaking changes between versions are no longer a problem, because one version of .NET Logging library is used.

The actual implementation of ILogger interface should be in different assembly and it should be only place where you reference .NET Logging library. In CompanyName.SomeSolution.Application in place where you compose your application you should now map ILogger abstraction to concrete implementation.

We are using that approach and we are also using NuGet for distribute our abstractions and our implementations. Unfortunately, issues with versions can appear with your own packages. To avoid that issues apply Semantic Versioning in packages you deploy via NuGet for your company. If something change in in your code base that is distributed via NuGet you should change in all of the packages that are distributed via NuGet. For example we have in our local NuGet server :

  • DomainModel
  • Services.Implementation.SomeFancyMessagingLibrary (that references DomainModel and SomeFancyMessagingLibrary)
  • and more...

Version between this packages are synchronized, if version is changed in DomainModel, the same version is in Services.Implementation.SomeFancyMessagingLibrary. If our applications needs update of our internal packages all dependencies are updated to the same version.

Arkadiusz K
  • 1,797
  • 1
  • 17
  • 18
  • 5
    So in fact I need to build an abstraction layer above *each* 3rd party library? In typical project I have 15 libraries with deeply integrated calls of them (example of such is System.Collections.Immutable package). Don't know if it is possible in all cases. – v.karbovnichy Sep 28 '15 at 13:10
  • I think this solution applies for each 3rd-party library that is a cross-cutting concern. I had to do the same thing with a logger recently. – Moby Disk Sep 28 '15 at 13:12
  • 1
    @kpa6uk You don't have to provide a wrapper with 1-1 method mapping for each class in a 3rd party library. You should always provide a wrapper that describes your *business logic*, and only accidentally it can have a 3rd party library inside. This way, your abstractions always describe what is *the need* to be satisfied. How is it satisfied is a secondary concern, low-level implementation detail. – BartoszKP Sep 28 '15 at 13:37
  • 1
    @BartoszKP I think that it's a really risky way of 'solving' a problem. I'd even say, that it's is not a solving he problem, but just moving it to first-party. It you cover only a part of a 3rd-party library with custom interface mapping, In future you will have to rewrite your wrapper library every time you need one another method of the original library. And it also might happen that some needed methods and approaches do really exist in one version of the original library – maiksaray Sep 29 '15 at 07:42
  • @maiksaray No, that's the best way to do it. You don't ever need "another method of a 3rd party library". You need to satisfy some new business requirement for your application. So, it's obvious that you have to add some methods, somewhere. If, by chance, you need to add a method to the wrapper and use another method of a 3rd party library, then so be it. Without the abstract wrapper you introduce another dependency on some classes that are results of this method. With the wrapper, you can write your own domain object. Unless you're talking about writing code for the sake of writing code. – BartoszKP Sep 29 '15 at 11:56
  • @BartoszKP I get it. I meant managing another additional library that satisfies all inner projects that are dependant on it(wrapper) inside the huge solution. Usually this wrapper would be written by separate team inside company. That is some complication for the solution and all dependant project. But, well, 5 hours later, i see clearer that it is still a huge simplification, thx =) – maiksaray Sep 29 '15 at 12:38
  • 2
    I agree that Unity should only be referenced by the top layer in the application, so dependent layers should be completely unaware of it. You forgot to mention that this is a classic example of a [facade pattern](https://sourcemaking.com/design_patterns/facade), which is typically used to simplify a complicated API into one that only contains the members that the application is interested in (but it doesn't have to map 1-1). All in all, this is a good approach for abstracting 3rd party libraries that deal with cross-cutting concerns. – NightOwl888 Oct 02 '15 at 15:40
14

You can work at post-compilation assembly level to solve this issue with...

Option 1

You could try merging the assemblies with ILMerge

ilmerge /target:winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll

The result will be an assembly that is the sum of your project and its required dependencies. This comes with some drawbacks, like sacrificing mono support and losing assembly identities (name, version, culture etc.), so this is best when all the assemblies to merge are built by you.

So here comes...

Option 2

You can instead embed the dependencies as resources within your projects as described in this article. Here is the relevant part:

At run-time, the CLR won’t be able to find the dependent DLL assemblies, which is a problem. To fix this, when your application initializes, register a callback method with the AppDomain’s ResolveAssembly event. The code should look something like this:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

   String resourceName = "AssemblyLoadingAndReflection." +

      new AssemblyName(args.Name).Name + ".dll";

   using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {

      Byte[] assemblyData = new Byte[stream.Length];

      stream.Read(assemblyData, 0, assemblyData.Length);

      return Assembly.Load(assemblyData);

   }

};

Now, the first time a thread calls a method that references a type in a dependent DLL file, the AssemblyResolve event will be raised and the callback code shown above will find the embedded DLL resource desired and load it by calling an overload of Assembly’s Load method that takes a Byte[] as an argument.

I think this is the option i would use if I were in your shoes, sacrificing some initial startup time.

Update

Have a look here. You could also try using those <probing> tags in each project's app.config to define a custom sub-folder to look in when the CLR searches for assemblies.

beppe9000
  • 1,056
  • 1
  • 13
  • 28
  • Wow, that's a really great way to manage dependencies of a project that is architectually stuck with old lib versions. But in solution's point of view, I'd say "Dll-hell intensifies". Of course, I know that we can't do anything about it with with tools that we have and API's constantly changing. Also, isn't it slow as hell and not assembly cached? Should we manually AOT compile this assemblies each time for performace to be somewhere near regular dependencies? – maiksaray Sep 29 '15 at 07:48
  • @maiksaray Have a look [here](https://msdn.microsoft.com/en-us/library/4191fzwb%28v=vs.110%29.aspx). You could try using those `` tags in each project's `.config`-file to define custom subfolder to look in when the CLR searchs for assemblies. – beppe9000 Sep 29 '15 at 11:55