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

Jul*_*anR 5 c# dll plugins appdomain .net-assembly

我们为一个产品建立了相当大的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的答案,并且PluginLoader在该方法的类的Gist中可以看到我的实现LoadReferencedAssemblies。这有两个问题:

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

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

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

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

Pan*_*nis 5

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

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

如果存在以下任一情况,则此方法的缺点:

  • 部署的程序集驻留在应用程序基础下的目录中。如果仅设置AssemblyName.Codebase属性,则可以克服此问题(需要付费,请参见Assembly.Load(AssemblyName)文档中的说明)。这将使Load像LoadFrom一样工作。MEF在中使用此方法。AssemblyCatalog
  • 您拥有具有相同标识的不同程序集(不仅仅是文件)。在这种情况下,您可能需要使用其他程序集命名方法。例如,DevExpress方法具有强名称的程序集,而程序集版本在文件名中。这将使您拥有平坦的目录结构。如果无法采用这种方法,请对程序集进行强命名,然后将其部署在GAC或其他文件夹中。如果您不能执行此操作,则所有程序集都将使用当前方法加载尽可能少的程序集,但请尝试制定计划,以新的强命名版本慢慢替换它们。

请注意,我对ServiceStack不熟悉。在ASP.NET中,可以使用该Assembly.Codebase属性加载在bin外部部署的程序集

最后,看看Suzanne Cook的关于LoadFile与LoadFrom的博客条目。