5

我们有一个相当大的 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 列表,而没有任何机会加载重复的程序集”,最好的解决方案是什么?:)

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

4

1 回答 1

5

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. Then replace all calls to Assembly.LoadFile with Assembly.Load. This will let the .NET assembly-resolving mechanism to automatically resolve all assemblies for you. 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.

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. This will make Load work like LoadFrom. MEF uses this approach in 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. 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. 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. In ASP.NET you can use the Assembly.Codebase property to load assemblies deployed outside of bin.

Finally have a look at Suzanne Cook's blog entry on LoadFile vs. LoadFrom.

于 2013-10-23T13:22:27.987 回答