Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve NuGet dependency hell

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.

like image 890
v.karbovnichy Avatar asked Sep 23 '15 12:09

v.karbovnichy


People also ask

How do I get NuGet package DLL?

Just build the class library by clicking the right click on solution explorer and you will see the NameOfLibrary. dll file in packages folder of your project directory. packages folder isn't displaying as folder in solution explorer. You can see them as References.

What is transitive dependency NuGet?

Dependency resolution with PackageReference. When installing packages into projects using the PackageReference format, NuGet adds references to a flat package graph in the appropriate file and resolves conflicts ahead of time. This process is referred to as transitive restore.

Why does NuGet fail to resolve dependencies?

As shown below, if Package A requires exactly Package B 1.0 and Package C requires Package B >=2.0, then NuGet cannot resolve the dependencies and gives an error. In these situations, the top-level consumer (the application or package) should add its own direct dependency on Package B so that the Nearest Wins rule applies.

How does NuGet handle different versions of a package?

When the package graph for an application contains different versions of the same package, NuGet chooses the package that's closest to the application in the graph and ignores all others. This behavior allows an application to override any particular package version in the dependency graph.

How to resolve NuGet package and reference version conflicts?

How to resolve .NET reference and NuGet package version conflicts 1 If possible, resolve to a single reference version. ... 2 Use a single reference versions or load versions side-by-side. ... 3 Solution 1: Use a single assembly version with Binding Redirect. ... 4 Strong names and the GAC. ... More items...

How do I find the dependencies of a package?

With packages.config, a project's dependencies are written to packages.config as a flat list. Any dependencies of those packages are also written in the same list. When packages are installed, NuGet might also modify the.csproj file, app.config, web.config, and other individual files.


2 Answers

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.

like image 51
Arkadiusz K Avatar answered Sep 23 '22 01:09

Arkadiusz K


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.

like image 32
beppe9000 Avatar answered Sep 22 '22 01:09

beppe9000