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

C# 将程序集作为模块/插件加载,同时避免重复和脆弱性,c#,plugins,dll,.net-assembly,appdomain,C#,Plugins,Dll,.net Assembly,Appdomain,我们有一个相当大的C#代码库,用于一个被分成许多程序集的产品,以避免一个单一的产品,并强制执行一些代码质量标准(特定于客户的功能包含在特定于客户的程序集中,以保持“核心”通用性,并且不受特定于客户的业务逻辑的依赖性的影响)。我们在内部称这些插件,但它们更多的是组成整个产品的模块 其工作方式是将这些模块的DLL复制到一个目录中,然后应用程序运行时(ServiceStack IIS web应用程序或基于Quartz的控制台应用程序)对当前已加载程序集列表中未加载的每个模块执行Assembly.Loa

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

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

这个
PluginLoader
只加载
plugins.config
文件中存在的程序集,但我认为这与当前的问题无关

PluginLoader
类的完整代码:

这个…可以工作。有点。但是它很脆弱,并且存在这样一个问题:程序集以这种方式加载两次,从不同的位置(通常是从应用程序的/bin/文件夹和插件目录)。这似乎是因为在调用
PluginLoader
类时,
AppDomain.CurrentDomain.GetAssemblys()
(启动时)不一定返回程序将自行加载的程序集的最终列表。因此,如果在/bin/中有名为dapper.dll的程序集(核心和许多插件/模块的共同依赖项)程序尚未使用,那么它将尚未加载(换句话说,它将延迟加载)。然后,如果该dapper.dll也是由插件加载的,
PluginLoader
将看到它尚未加载,并将加载它。然后,当程序使用其dapper依赖项时,它将从/bin/加载dapper.dll,我们现在加载了两个dapper.dll

在大多数情况下,这似乎很好。但是,我们使用RazorEngine库,它在您尝试编译模板时抱怨使用相同名称的重复程序集

在调查过程中,我遇到了一个问题:

我尝试了公认的答案和Jon Skeet的解决方案。公认的答案有效(尽管我还没有验证是否有任何奇怪的行为)但这感觉很糟糕。首先,这也让程序尝试加载碰巧在/bin/中的本机DLL,但显然失败了,因为它们不是.NET程序集。因此,您现在必须尝试解决这个问题。我还担心如果/bin/包含一些不再实际使用的旧DLL,但现在仍然被加载,会产生奇怪的副作用。这在生产中不是问题,但在开发中是问题(事实上,这整件事在开发中更像是问题,而不是生产,但解决这一问题的附加健壮性在生产中也会得到赞赏)

如上所述,我还尝试了Jon Skeet的答案,我的实现在方法
LoadReferencedAssemblys
中的
PluginLoader
类的要点中可见。这有两个问题:

  • 它在某些程序集名称上失败,例如未找到文件的
    System.Runtime.Serialization
  • 它会导致一个失败,插件突然找不到依赖项。我还不知道为什么
  • 我还简要研究了使用托管可扩展性框架,但我不确定它是否适用。这似乎更旨在提供一个框架来加载组件并定义它们如何交互,而实际上我只对动态加载程序集感兴趣

    因此,考虑到“我想从目录中动态加载指定的DLL列表,而不可能加载重复的程序集”的要求,最好的解决方案是什么?:)


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

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

    最简单的方法是将所有文件夹添加到您的专用探测路径中。然后将对
    Assembly.LoadFile
    的所有调用替换为。这将使.NET程序集解析机制自动为您解析所有程序集。您不需要加载引用的程序集,因为它们将在nee时加载自动加载。只需使用
    Assembly.Load
    加载模块/插件

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

    • 部署的程序集不驻留在应用程序库下的目录中。如果您只设置属性,则可以克服这一问题(有成本,请参阅文档中的备注)。这将使Load像LoadFrom一样工作。MEF在
      AssemblyCatalog
      中使用此方法
    • 您有不同的程序集(不仅仅是文件)使用相同的标识。如果是这种情况,则可能需要使用不同的程序集命名方法。例如,DevExpress方法具有文件名中具有程序集版本的强命名程序集。这将允许您使用平面目录结构。如果无法使用此方法,则ong命名你的程序集并将它们部署在GAC或不同的文件夹中。如果你不能做到这一点,那么你的所有程序集都将使用当前方法加载尽可能少的程序集,但请尝试一个计划,慢慢用新的强命名版本替换它们
    请注意,我不熟悉ServiceStack。在ASP.NET中,您可以使用
    Assembly.Codebase
    属性来创建

    最后看一下Suzanne Cook在上的博客。

    我可以问你为什么需要