[英]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: 这有两个问题:
System.Runtime.Serialization
with a file not found. 它在某些程序集名称上失败,例如System.Runtime.Serialization
,找不到文件。 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: 如果存在以下任一情况,则此方法的缺点:
AssemblyCatalog
. MEF在AssemblyCatalog
使用此方法。 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.