我开发了一个包含一些名为CompanyName.SDK
的功能的库,它必须集成在公司项目CompanyName.SomeSolution
CompanyName.SDK.dll
。
CompanyName.SDK
包依赖于第三方NuGet包。举个好例子,我们来看Unity
。当前依赖关系位于v3.5.1405-prerelease
的{{1}}。
Unity
取决于CompanyName.SomeSolution.Project1
Unity
。
v2.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.config
,Project1
和Project2
中的代码如何使用他们编码,编译和测试的完全需要的依赖包版本?
注1: SDK
只是一个例子,第三方模块依赖于另一个第三方模块,实际情况要差10倍,而第三方模块又同时有3-4个版本。
注意2:我无法将所有软件包升级到最新版本,因为有些软件包依赖于非最新版本的软件包。
注3:假设依赖包在版本之间有重大变化。这是我提出这个问题的真正问题。
NOTE4:我知道问题about conflicts between different versions of the same dependent assembly,但那里的答案并没有解决问题的根源 - 他们只是隐藏它。
NOTE5:哪里到底是承诺“DLL Hell”问题的解决方案?它只是从另一个位置重新出现。
NOTE6:如果您认为使用GAC是某种选择,请编写分步指南,或者给我一些链接。
答案 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.Project1
和CompanyName.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
(引用DomainModel
和SomeFancyMessagingLibrary
)此软件包之间的版本已同步,如果DomainModel
中的版本已更改,则Services.Implementation.SomeFancyMessagingLibrary
中的版本相同。如果我们的应用程序需要更新我们的内部包,则所有依赖项都会更新为相同的版本。
答案 1 :(得分:12)
您可以在编译后汇编级别工作,以解决此问题......
您可以尝试使用jsfiddle
合并程序集ilmerge / target:winexe /out:SelfContainedProgram.exe Program.exe ClassLibrary1.dll ClassLibrary2.dll
结果将是一个程序集,它是项目及其所需依赖项的总和。这带来了一些缺点,比如牺牲单声道支持和丢失程序集标识(名称,版本,文化等),所以当你构建所有要合并的程序集时,这是最好的。
所以来了......
您可以将依赖项作为资源嵌入到项目中,如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搜索程序集时查看。