我们有一个相当大的 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
。这有两个问题:
- 它在某些程序集名称上失败,例如
System.Runtime.Serialization
找不到文件。 - 它会在稍后导致插件突然无法找到依赖项时发生故障。我还找不到原因。
我还简要调查了使用托管可扩展性框架,但我不确定它是否适用。这似乎更旨在为加载组件和定义它们如何交互提供一个框架,而我实际上只对动态加载程序集感兴趣。
那么,鉴于要求“我想从目录中动态加载指定的 DLL 列表,而没有任何机会加载重复的程序集”,最好的解决方案是什么?:)
如果需要的话,我愿意彻底检查插件系统的工作方式。