我们有一个相当大的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
类的要点中可见。这有两个问题:
System.Runtime.Serialization
。 我还简要介绍了使用Managed Extensibility Framework,但我不确定它是否适用。这似乎更多的目的是为加载组件和定义它们如何交互提供框架,而我实际上只对动态加载程序集感兴趣。
因此,鉴于要求“我想从目录中动态加载指定的DLL列表而没有任何加载重复程序集的机会”,最佳解决方案是什么? :)
我愿意彻底改变插件系统的运作方式,如果这就是它。
答案 0 :(得分:5)
有几种方法可以解决这个问题(模块/插件部署在目录层次结构中),坦率地说,你选择了最困难的方法。
最简单一个是在app/web.config的私有探测路径中添加所有文件夹。然后将Assembly.LoadFile
的所有来电替换为Assembly.Load。这将让.NET程序集解析机制自动为您解析所有程序集。您不需要加载引用的程序集,因为它们将在需要时自动加载。只需使用Assembly.Load
加载模块/插件。
这种方法的缺点是当下列任何一种情况属实时:
AssemblyCatalog
。请注意,我不熟悉ServiceStack。在ASP.NET中,您可以使用Assembly.Codebase
属性load assemblies deployed outside of bin。
最后看看Suzanne Cook在LoadFile vs. LoadFrom上的博客文章。