如何解决NuGet依赖地狱

时间:2015-09-23 12:27:39

标签: c# .net nuget dependency-management

我开发了一个包含一些名为CompanyName.SDK的功能的库,它必须集成在公司项目CompanyName.SomeSolution

中 必须通过NuGet包部署

CompanyName.SDK.dllCompanyName.SDK包依赖于第三方NuGet包。举个好例子,我们来看Unity。当前依赖关系位于v3.5.1405-prerelease的{​​{1}}。

Unity取决于CompanyName.SomeSolution.Project1 Unityv2.1.505.2取决于CompanyName.SomeSolution.Project2 Unity

v3.0.1304.1集成到此解决方案中会增加对CompanyName.SDK Unity的依赖性。 我们假设v3.5.1405-prerelease有一个可运行的输出项目CompanyName.SomeSolution,该项目取决于上面和CompanyName.SomeSolution.Application上的两个

这里的问题就开始了。所有CompanyName.SDK程序集在所有包中都没有相同的名称,没有版本说明符。在目标文件夹中,只有Unity程序集的一个版本:Unity通过v3.5.1405-prerelease中的bindingRedirect

app.configProject1Project2中的代码如何使用他们编码,编译和测试的完全需要的依赖包版本?

注1: SDK只是一个例子,第三方模块依赖于另一个第三方模块,实际情况要差10倍,而第三方模块又同时有3-4个版本。

注意2:我无法将所有软件包升级到最新版本,因为有些软件包依赖于非最新版本的软件包。

注3:假设依赖包在版本之间有重大变化。这是我提出这个问题的真正问题。

NOTE4:我知道问题about conflicts between different versions of the same dependent assembly,但那里的答案并没有解决问题的根源 - 他们只是隐藏它。

NOTE5:哪里到底是承诺“DLL Hell”问题的解决方案?它只是从另一个位置重新出现。

NOTE6:如果您认为使用GAC是某种选择,请编写分步指南,或者给我一些链接。

2 个答案:

答案 0 :(得分:14)

Unity包不是一个好例子,因为您只应在名为Composition Root的地方使用它。并且Composition Root应尽可能接近应用程序入口点。在您的示例中,它是CompanyName.SomeSolution.Application

除此之外,我现在在哪里工作,出现了完全相同的问题。而我所看到的,问题通常是由日志记录等跨领域问题引入的。您可以应用的解决方案是将第三方依赖项转换为第一方依赖项。您可以通过引入该概念的抽象来实现。实际上,这样做有其他好处,如:

  • 更多可维护的代码
  • 更好的可测试性
  • 摆脱不必要的依赖(CompanyName.SDK的每个客户真正需要Unity依赖?)

所以,我们来看一个假想的.NET Logging库示例:

CompanyName.SDK.dll取决于.NET Logging 3.0
CompanyName.SomeSolution.Project1取决于.NET Logging 2.0
CompanyName.SomeSolution.Project2取决于.NET Logging 1.0

.NET Logging版本之间存在重大变化。

您可以通过引入ILogger接口:

来创建自己的第一方依赖关系
public interface ILogger
{
    void LogWarning();
    void LogError();
    void LogInfo();
} 

CompanyName.SomeSolution.Project1CompanyName.SomeSolution.Project2应使用ILogger界面。它们依赖于ILogger接口第一方依赖。现在,您将.NET Logging库放在一个地方后面,并且很容易执行更新,因为您必须在一个地方执行此操作。同时打破版本之间的更改不再是问题,因为使用了一个版本的.NET Logging库。

ILogger接口的实际实现应该在不同的程序集中,它应该只是您引用.NET Logging库的地方。 在组成应用程序的CompanyName.SomeSolution.Application中,您现在应该将ILogger抽象映射到具体实现。

我们正在使用这种方法,我们也使用NuGet来分发我们的抽象和我们的实现。不幸的是,版本问题可能会出现在您自己的软件包中。为避免该问题,请在您通过NuGet为您的公司部署的软件包中应用Semantic Versioning。如果通过NuGet分发的代码库中发生了某些变化,则应更改通过NuGet分发的所有包。例如,我们在本地NuGet服务器中:

  • DomainModel
  • Services.Implementation.SomeFancyMessagingLibrary(引用DomainModelSomeFancyMessagingLibrary
  • 以及......

此软件包之间的版本已同步,如果DomainModel中的版本已更改,则Services.Implementation.SomeFancyMessagingLibrary中的版本相同。如果我们的应用程序需要更新我们的内部包,则所有依赖项都会更新为相同的版本。

答案 1 :(得分:12)

您可以在编译后汇编级别工作,以解决此问题......

选项1

您可以尝试使用jsfiddle

合并程序集
  

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

结果将是一个程序集,它是项目及其所需依赖项的总和。这带来了一些缺点,比如牺牲单声道支持和丢失程序集标识(名称,版本,文化等),所以当你构建所有要合并的程序集时,这是最好的。

所以来了......

选项2

您可以将依赖项作为资源嵌入到项目中,如ILMerge中所述。以下是相关部分(重点是我的):

  

在运行时,CLR将无法找到依赖DLL   组件,这是一个问题。要解决这个问题,请在申请时   初始化,使用AppDomain注册回调方法   ResolveAssembly 事件。代码看起来像这样:

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);

   }

};
  

现在,第一次线程调用引用类型的方法   一个依赖的DLL文件,将会引发AssemblyResolve事件   上面显示的回调代码将找到所需的嵌入式DLL资源   并通过调用Assembly的Load方法的重载来加载它   将Byte []作为参数。

我认为这是我可以使用的选项,如果我在你的鞋子里,牺牲一些初始启动时间。

更新

看看this article。您还可以尝试在每个项目的app.config中使用这些<probing>标记来定义自定义子文件夹,以便在CLR搜索程序集时查看。