【发布时间】:2013-10-21 14:53:51
【问题描述】:
我们有一个相当大的 C# 代码库,该产品已被分成许多程序集,以避免单一的产品并强制执行一些代码质量标准(客户特定的功能进入客户特定的程序集中以保持“核心”通用且不受对客户特定业务逻辑的依赖的影响)。我们在内部调用这些插件,但它们更多的是构成整个产品的模块。
其工作方式是将这些模块的 DLL 复制到一个目录中,然后应用程序运行时(ServiceStack IIS Web 应用程序或基于 Quartz 的控制台应用程序)为每个不存在的模块执行 Assembly.LoadFile在已加载的当前程序集列表中 (AppDomain.CurrentDomain.GetAssemblies())。
这个PluginLoader 只加载存在于plugins.config 文件中的程序集,但我认为这与手头的问题几乎无关。
PluginLoader 类的完整代码:
https://gist.github.com/JulianRooze/9f6d1b5e61c855579203
这....有效。有点。虽然它很脆弱,并且存在一个问题,即程序集以这种方式从不同的位置加载两次(通常来自应用程序的 /bin/ 文件夹和插件目录)。这似乎是因为在调用PluginLoader 类时,AppDomain.CurrentDomain.GetAssemblies()(在启动时)不一定返回程序将自行加载的程序集的最终列表。因此,如果 /bin/ 中有一个名为 dapper.dll 的程序集(核心和许多插件/模块的公共依赖项)尚未被程序使用,那么它还没有被加载(换句话说:它会懒惰地加载它们)。然后,如果该 dapper.dll 也是由插件提供的,PluginLoader 将看到它尚未加载并将加载它。然后,当程序使用它的 Dapper 依赖项时,它会从 /bin/ 加载 dapper.dll,我们现在已经加载了两个 dapper.dll。
在大多数情况下,这似乎没问题。但是,我们使用 RazorEngine 库,当您尝试编译模板时,该库会抱怨具有相同名称的重复程序集。
在调查这个问题时,我遇到了这个问题:
Is there a way to force all referenced assemblies to be loaded into the app domain?
我尝试了接受的答案和 Jon Skeet 的解决方案。接受的答案有效(尽管我还没有验证是否有任何奇怪的行为)但感觉很讨厌。一方面,这也使程序尝试加载恰好位于 /bin/ 中的本机 DLL,这显然会失败,因为它们不是 .NET 程序集。所以你现在必须尝试-捕捉-吞下它。如果 /bin/ 包含一些实际上不再使用但现在被加载的旧 DLL,我也担心奇怪的副作用。这在生产中不是问题,但它正在开发中(事实上,整个问题在开发中比在生产中更成问题,但在生产中也将赞赏解决此问题的额外稳健性)。
如前所述,我也尝试了 Jon Skeet 的答案,我的实现在方法 LoadReferencedAssemblies 中的 PluginLoader 类的 Gist 中可见。这有两个问题:
- 它在某些程序集名称上失败,例如
System.Runtime.Serialization找不到文件。 - 它会在稍后导致插件突然无法找到依赖项时出现故障。我还找不到原因。
我还使用托管可扩展性框架进行了简要调查,但我不确定它是否适用。这似乎更旨在为加载组件和定义它们如何交互提供一个框架,而我实际上只对动态加载程序集感兴趣。
那么,鉴于要求“我想从目录中动态加载指定的 DLL 列表,而没有任何机会加载重复的程序集”,最好的解决方案是什么? :)
如果需要的话,我愿意彻底检查插件系统的工作方式。
【问题讨论】:
-
请问您为什么需要在不同文件夹中复制多个 dll 副本?
-
您是否评估过为此使用 MEF 或 Castle Windsor 的可能性?
-
@VladL - 我不需要它,它只是发生了。如果插件依赖于 libray X.dll,它将在插件目录中,如果 X.dll 也是程序本身的依赖项,它也将在 /bin/ 中。
-
@galenus - 我在我的问题中提到了 MEF,但我认为 MEF 并不适合我,因为它似乎针对的是类/接口级别以及它们如何交互,而我只对加载一堆 DLL 感兴趣,仅此而已。我没有要导出的类型或接口,没有组件,也没有想要扩展的东西。这只是程序集。
-
@JulianR 您不需要将 dll 复制到输出文件夹中。您可以在 app.config 文件中定义它们的路径
标签: c# plugins dll .net-assembly appdomain