我们为一个产品提供了一个相当大的 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 库,当您尝试编译模板时,它会抱怨具有相同名称的重复程序集。
在调查这个问题时,我遇到了这个问题:
有没有办法强制将所有引用的程序集加载到应用程序域中?
我尝试了公认的答案和Jon Skeet的解决方案。接受的答案有效(尽管我尚未验证是否有任何奇怪的行为),但感觉很讨厌。首先,这也使程序尝试加载恰好位于/bin/中的本机 DLL,这显然失败,因为它们不是 .NET 程序集。所以你现在必须尝试吞下这个。我也担心如果/bin/包含一些实际上不再使用的旧 DLL,但现在无论如何都会加载,我会产生奇怪的副作用。这不是生产中的问题,但它在开发中(事实上,这整个事情在开发中比在生产中更像是一个问题,但是解决这个问题的额外健壮性在生产中也会受到赞赏)。
如前所述,我也尝试了 Jon Skeet 的答案,我的实现在方法 LoadReferencedAssemblies
中PluginLoader
类的要点中可见。这有两个问题:
- 它在某些程序集名称上失败,例如找不到文件的
System.Runtime.Serialization
。 - 它会导致稍后插件突然找不到依赖项的失败。我还找不到原因。
我还简要研究了使用托管扩展性框架,但我不确定它是否适用。这似乎更旨在提供一个用于加载组件的框架并定义它们如何交互,而我实际上只对动态加载程序集感兴趣。
因此,鉴于要求"我想从目录中动态加载指定的 DLL 列表,而没有任何机会加载重复程序集",最佳解决方案是什么? :)
我愿意彻底改革插件系统的工作方式,如果这是需要的。
有几种方法可以解决这个问题(模块/插件部署在目录层次结构中),坦率地说,您选择了最困难的方法。
最简单的方法是将所有文件夹添加到 app/web.config 的专用探测路径中。然后将所有对Assembly.LoadFile
的调用替换为Assembly.Load。这将允许 .NET 程序集解析机制自动为您解析所有程序集。您不需要加载引用的程序集,因为它们将在需要时自动加载。只有模块/插件必须使用 Assembly.Load
加载。
此方法的缺点是当满足以下任一条件时:
- 部署的程序集不驻留在应用程序库下的目录中。如果您只设置 AssemblyName.Codebase 属性,则可以解决此问题(但需要付出代价,请参阅 Assembly.Load(AssemblyName) 文档中的备注)。这将使 Load 像 LoadFrom 一样工作。MEF 在
AssemblyCatalog
中使用此方法。 - 您具有具有相同标识的不同程序集(不仅仅是文件)。如果是这种情况,则可能需要使用不同的程序集命名方法。以 DevExpress 方法为例,该方法具有具有强名称的程序集,文件名中包含程序集版本。这将允许您拥有一个平面目录结构。如果不能使用此方法,请为程序集添加强名称,并将其部署在 GAC 或其他文件夹中。如果您无法做到这一点,那么您的所有程序集都将使用当前方法加载尽可能少的程序集,但请尝试使用新的强名称版本慢慢替换它们的计划。
请注意,我不熟悉ServiceStack。在 ASP.NET 中,可以使用 Assembly.Codebase
属性加载部署在 bin 外部的程序集。
最后看看Suzanne Cook关于LoadFile vs. LoadFrom的博客文章。