将程序集加载为模块/插件,同时避免重复和脆弱性

本文关键字:脆弱性 加载 程序集 模块 插件 | 更新日期: 2023-09-27 18:36:11

我们为一个产品提供了一个相当大的 C# 代码库,它被分成许多程序集,以避免整体式产品并强制实施一些代码质量标准(特定于客户的功能包含在特定于客户的程序集中,以保持"核心"泛型,不受对客户特定业务逻辑的依赖的阻碍)。我们在内部调用这些插件,但它们更多的是构成整个产品的模块。

其工作方式是将这些模块的DLL复制到目录中,然后应用程序运行时(ServiceStack IIS Web应用程序或基于Quartz的控制台应用程序)对不在当前已加载程序集列表中的每个模块执行Assembly.LoadFileAppDomain.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 的答案,我的实现在方法 LoadReferencedAssembliesPluginLoader类的要点中可见。这有两个问题:

  1. 它在某些程序集名称上失败,例如找不到文件的System.Runtime.Serialization
  2. 它会导致稍后插件突然找不到依赖项的失败。我还找不到原因。

我还简要研究了使用托管扩展性框架,但我不确定它是否适用。这似乎更旨在提供一个用于加载组件的框架并定义它们如何交互,而我实际上只对动态加载程序集感兴趣。

因此,鉴于要求"我想从目录中动态加载指定的 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的博客文章。