简体   繁体   English

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

[英]Loading assemblies as modules/plugins while avoiding duplication and fragility

We have a fairly large C# code base for a product that has been separated into many assemblies to avoid a monolithic product and to enforce some code quality standards (customer-specific features go in customer-specific assemblies to keep the "core" generic and unencumbered by dependencies on customer-specific business logic). 我们有一个相当大的C#代码库,该产品已分成许多程序集,以避免整体产品,并强制执行某些代码质量标准(特定于客户的功能包含在特定于客户的程序集中,以保持“核心”通用且不受限制)取决于客户特定的业务逻辑)。 We call these plugins internally, but they're more the modules that make up the total product. 我们在内部将这些插件称为“插件”,但它们更多是构成整个产品的模块。

The way this works is that the DLLs of these modules get copied to a directory, the application runtime (either a ServiceStack IIS web application or a Quartz-based console application) then does an Assembly.LoadFile for every module that is not in the list of current assemblies already loaded ( AppDomain.CurrentDomain.GetAssemblies() ). 这种工作方式是将这些模块的DLL复制到目录中,然后在应用程序运行时(ServiceStack IIS Web应用程序或基于Quartz的控制台应用程序)对不在列表中的每个模块执行Assembly.LoadFile 。已加载的当前程序集( AppDomain.CurrentDomain.GetAssemblies() )。

This PluginLoader only loads assemblies that are present in plugins.config file, but I think that's mostly irrelevant for the problem at hand. PluginLoader仅加载plugins.config文件中存在的程序集,但我认为与手头的问题几乎没有关系。

The full code for the PluginLoader class: PluginLoader类的完整代码:

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

This.... works. 这个...。 Sort of. 有点。 It's fragile though and suffers from a problem that assemblies get loaded twice this way, from different locations (usually from the /bin/ folder of the application and the plugin directory). 但是它很脆弱,并且遇到一个问题,即程序集从不同的位置(通常是从应用程序的/ bin /文件夹插件目录)以这种方式两次加载。 This seems to happen because at the moment the PluginLoader class is invoked, the AppDomain.CurrentDomain.GetAssemblies() (at startup) does not necessarily return the final list of the assemblies that the program will load by itself. 发生这种情况似乎是因为在调用PluginLoader类时, AppDomain.CurrentDomain.GetAssemblies() (在启动时)不一定返回程序将自行加载的程序集的最终列表。 So if there's an assembly in the /bin/ called dapper.dll (a common dependency of both the core and many plugins/modules) that has not been used by the program yet, then it will not have been loaded yet (in other words: it loads them lazily). 因此,如果/ bin /中有一个名为dapper.dll的程序集(内核和许多插件/模块的常见依赖项)尚未被程序使用,则它将尚未加载(换句话说, :它会延迟加载它们)。 Then, if that dapper.dll is also by a plugin, the PluginLoader will see that it has not been loaded yet and will load it. 然后,如果该dapper.dll也由插件提供,则PluginLoader将看到尚未加载该文件,并将对其进行加载。 Then, when the program uses its Dapper dependency, it will load the dapper.dll from /bin/ and we now have two dapper.dll's loaded. 然后,当程序使用其Dapper依赖项时,它将从/ bin /加载dapper.dll,现在我们已经加载了两个dapper.dll。

In most cases, that seems to be fine. 在大多数情况下,这似乎很好。 However, we make use of the RazorEngine library which complains about duplicate assemblies with the same name when you try to compile your templates. 但是,我们使用RazorEngine库,该库在您尝试编译模板时会抱怨名称相同的重复程序集。

In investigating this, I came across this question: 在对此进行调查时,我遇到了一个问题:

Is there a way to force all referenced assemblies to be loaded into the app domain? 有没有一种方法可以强制将所有引用的程序集加载到应用程序域中?

I tried both the accepted answer and Jon Skeet's solution. 我尝试了接受的答案和Jon Skeet的解决方案。 The accepted answer works (though I haven't verified if there's any odd behavior yet) but it feels nasty. 可接受的答案有效(尽管我还没有验证是否有任何奇怪的行为),但是感觉很讨厌。 For one, this also makes the program try to load native DLLs that happen to be in /bin/ which obviously fails because they're not .NET assemblies. 一方面,这也使程序尝试加载恰好在/ bin /中的本机DLL,这显然会失败,因为它们不是.NET程序集。 So you now have to try-catch-swallow this. 因此,您现在必须尝试捕获该错误。 I'm also worried about weird side effects if the /bin/ contains some old DLL that isn't actually used any more, but now gets loaded anyway. 如果/ bin /包含一些实际上不再使用的旧DLL,但现在无论如何都会加载,我也担心会有奇怪的副作用。 That isn't a problem in production, but it is in development (in fact, this whole thing is more of a problem in dev than production, but the added robustness of solving this would be appreciated in production as well). 那不是生产中的问题,而是开发中的问题(实际上,这整个问题在开发中比在生产中更多的是问题,但是解决此问题的附加健壮性在生产中也会受到赞赏)。

As said, I also tried Jon Skeet's answer, and my implementation is visible in that Gist of the PluginLoader class in the method LoadReferencedAssemblies . 至于说,我也试过乔恩斯基特的答案,我的实现是在那主旨可见的PluginLoader类的方法LoadReferencedAssemblies This has two problems: 这有两个问题:

  1. It fails on some assembly names, like System.Runtime.Serialization with a file not found. 它在某些程序集名称上失败,例如System.Runtime.Serialization ,找不到文件。
  2. It causes a failure later where a plugin is suddenly unable to find a dependency. 稍后会导致失败,导致插件突然找不到依赖项。 I can't find why yet. 我还找不到原因。

I also briefly investigated using Managed Extensibility Framework, but I'm not sure if it applies. 我还使用托管扩展框架进行了简短的调查,但不确定是否适用。 That seems to be more aimed at providing a framework for loading components and defining how they can interact, whereas I'm literally only interested in loading assemblies dynamically. 这似乎更旨在为提供一个用于加载组件并定义它们如何交互的框架,而我实际上只是对动态加载程序集感兴趣。

So, given the requirement "I want to dynamically load a specified list of DLLs from a directory without any chance of loading duplicate assemblies", what is the best solution? 因此,考虑到“我想从目录中动态加载DLL的指定列表,而又没有加载重复程序集的机会”的要求,最佳解决方案是什么? :) :)

I'm willing to overhaul how the plugin system works, if that's what it takes. 如果愿意的话,我愿意彻底检查插件系统的工作方式。

There are a few ways to tackle this problem (modules/plugins deployed in a hierarchy of directories) and frankly, you have chosen the most difficult. 有几种方法可以解决此问题(在目录层次结构中部署模块/插件),坦率地说,您选择了最困难的方法。

The simplest one is to add all your folders in the private probing path of your app/web.config . 最简单的方法是将所有文件夹添加到app / web.config的专用探测路径中。 Then replace all calls to Assembly.LoadFile with Assembly.Load . 然后将所有对Assembly.LoadFile调用替换为Assembly.Load This will let the .NET assembly-resolving mechanism to automatically resolve all assemblies for you. 这将使.NET程序集解析机制可以自动为您解析所有程序集。 You will not need to load referenced assemblies since they will be loaded when needed automatically. 您将不需要加载引用的程序集,因为它们将在需要时自动加载。 Only the modules/plugins will have to be loaded using Assembly.Load . 仅模块/插件将必须使用Assembly.Load加载。

The drawbacks of this approach is when either one of the following is true: 如果存在以下任一情况,则此方法的缺点:

  • The deployed assemblies do not reside in directories under the application base. 部署的程序集驻留在应用程序基础下的目录中。 This can be overcome (at a cost, see the remarks in the Assembly.Load(AssemblyName) documentation) if you just set the AssemblyName.Codebase property. 如果仅设置AssemblyName.Codebase属性,则可以克服此问题(需要付费,请参见Assembly.Load(AssemblyName)文档中的说明)。 This will make Load work like LoadFrom. 这将使Load像LoadFrom一样工作。 MEF uses this approach in AssemblyCatalog . MEF在AssemblyCatalog使用此方法。
  • You have different assemblies (not just files) with the same identity. 您拥有具有相同标识的不同程序集(不仅仅是文件)。 If this is the case then you probably need to use a different assembly naming approach. 在这种情况下,您可能需要使用其他程序集命名方法。 Take for example the DevExpress approach that has strong-named assemblies with the assembly version in the file name. 例如,DevExpress方法具有强名称的程序集,而程序集版本在文件名中。 This will allow you to have a flat directory structure. 这将使您拥有平坦的目录结构。 If you cannot go with this approach, then strong-name your assemblies and deploy them in the GAC or in different folders. 如果您不能采用这种方法,则对程序集进行强命名,然后将其部署在GAC或其他文件夹中。 If you cannot do this will all your assemblies then load as few assemblies as possible with your current approach but try male a plan to slowly replace them with new strong-named versions. 如果您无法执行此操作,则所有程序集都将使用当前方法加载尽可能少的程序集,但请尝试制定一个计划,以新的强命名版本慢慢替换它们。

Note that I am not familiar with ServiceStack. 请注意,我对ServiceStack不熟悉。 In ASP.NET you can use the Assembly.Codebase property to load assemblies deployed outside of bin . 在ASP.NET中,可以使用Assembly.Codebase属性加载在bin外部部署的程序集

Finally have a look at Suzanne Cook's blog entry on LoadFile vs. LoadFrom . 最后,看看Suzanne Cook的关于LoadFile与LoadFrom的博客条目。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM