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

标签 c# plugins dll .net-assembly appdomain

我们有一个相当大的 C# 代码库,该产品已被分成许多程序集,以避免单一的产品并强制执行一些代码质量标准(客户特定的功能进入客户特定的程序集中,以保持“核心”通用且不受阻碍通过依赖于客户特定的业务逻辑)。我们在内部调用这些插件,但它们更多的是构成整个产品的模块。

其工作方式是将这些模块的 DLL 复制到一个目录,然后应用程序运行时(ServiceStack IIS Web 应用程序或基于 Quartz 的控制台应用程序)执行 Assembly.LoadFile对于不在已加载的当前程序集列表中的每个模块 (AppDomain.CurrentDomain.GetAssemblies())。

这个PluginLoader仅加载 plugins.config 中存在的程序集文件,但我认为这与手头的问题几乎无关。
PluginLoader 的完整代码类(class):

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/中的 native DLL,这显然会失败,因为它们不是 .NET 程序集。所以你现在必须尝试-捕捉-吞下它。如果/bin/包含一些实际上不再使用但现在被加载的旧 DLL,我也担心奇怪的副作用。这在生产中不是问题,但它正在开发中(事实上,整个问题在开发中比在生产中更成问题,但在生产中也将赞赏解决此问题的额外稳健性)。

如前所述,我也尝试了 Jon Skeet 的回答,我的实现在 PluginLoader 的要点中可见。方法中的类LoadReferencedAssemblies .这有两个问题:

  • 它在某些程序集名称上失败,例如 System.Runtime.Serialization找不到文件。
  • 它会在稍后导致插件突然无法找到依赖项时发生故障。我还找不到原因。

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

    那么,鉴于要求“我想从目录中动态加载指定的 DLL 列表,而没有任何机会加载重复的程序集”,最好的解决方案是什么? :)

    如果需要的话,我愿意彻底检查插件系统的工作方式。

    最佳答案

    有几种方法可以解决这个问题(模块/插件部署在目录层次结构中),坦率地说,您选择了最困难的。

    最简单的一种是将所有文件夹添加到您的app/web.config 的私有(private)探测路径中。 .然后将所有调用替换为 Assembly.LoadFileAssembly.Load .这将使 .NET 程序集解析机制自动为您解析所有程序集。您不需要加载引用的程序集,因为它们会在需要时自动加载。只有模块/插件必须使用 Assembly.Load 加载.

    这种方法的缺点是当以下任一情况为真时:

  • 部署的程序集执行不是 驻留在应用程序库下的目录中。如果您只设置 Assembly.Load(AssemblyName),则可以克服此问题(需要付出代价,请参阅 AssemblyName.Codebase 文档中的注释)。属性(property)。这将使 Load 像 LoadFrom 一样工作。 MEF 在 AssemblyCatalog 中使用这种方法.
  • 有不同的程序集(不仅仅是文件)相同身份。如果是这种情况,那么您可能需要使用不同的程序集命名方法。以 DevExpress 方法为例,该方法具有强名称程序集,文件名中包含程序集版本。这将允许您拥有一个平面目录结构。如果您不能采用这种方法,则对您的程序集进行强命名并将它们部署在 GAC 或不同的文件夹中。如果您不能这样做,那么您的所有程序集都将使用您当前的方法加载尽可能少的程序集,但请尝试使用新的强名称版本慢慢替换它们的计划。

  • 请注意,我不熟悉 ServiceStack。在 ASP.NET 中,您可以使用 Assembly.Codebase属性(property)到load assemblies deployed outside of bin .

    最后看看 Suzanne Cook 在 LoadFile vs. LoadFrom 上的博客文章。 .

    关于c# - 将程序集作为模块/插件加载,同时避免重复和脆弱性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19497831/

    相关文章:

    c# - 如何在 C# 中解析/反序列化从 rest 服务返回的 JSON

    c# - 更新 Facebook 访问 token

    java - JNI 和 DLL : Two way interaction (callback)

    php - 用 C++ 扩展 PHP?

    javascript - 在 R Leaflet 中实现 (javascript) 插件

    c# - 我可以将 C DLL 的输出重定向到我的 c# log4net 输出吗

    c++ - 将 DLL 中的非导出函数声明为静态的原因

    c# - 解析 Datetime c# 的奇怪结果

    c# - 什么是 '=>' ? (C#语法题)

    python - 如何使用 setuptools 注册多个 Pluggy 插件