将组件作为模块/插件加载,同时避免重复和脆弱

时间:2013-10-21 14:53:52

标签: c# plugins dll .net-assembly appdomain

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

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

PluginLoader仅加载plugins.config文件中存在的程序集,但我认为这与手头的问题无关。

PluginLoader类的完整代码:

https://gist.github.com/JulianRooze/9f6d1b5e61c855579203

这....工作。有点。它虽然很脆弱,但是会遇到这样一个问题:程序集从不同的位置(通常来自应用程序和/ / em>插件目录的/ 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的答案,我的实现在方法PluginLoader中的LoadReferencedAssemblies类的要点中可见。这有两个问题:

  1. 某些程序集名称失败,例如找不到文件的System.Runtime.Serialization
  2. 如果插件突然无法找到依赖项,则会导致失败。我找不到原因。
  3. 我还简要介绍了使用Managed Extensibility Framework,但我不确定它是否适用。这似乎更多的目的是为加载组件和定义它们如何交互提供框架,而我实际上只对动态加载程序集感兴趣。

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

    我愿意彻底改变插件系统的运作方式,如果这就是它。

1 个答案:

答案 0 :(得分:5)

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

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

这种方法的缺点是当下列任何一种情况属实时:

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

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

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