通过MEF动态加载.NET插件时,如何使其binding redirect生效?
我有一个.NET 4.8类库,通过MEF作为插件加载。该类库的部分依赖项自动生成了binding redirect,配置文件MyLibrary.dll.config内容如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" /> </dependentAssembly> </assemblyBinding> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" /> </dependentAssembly> </assemblyBinding> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2" /> </dependentAssembly> </assemblyBinding> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" /> </dependentAssembly> </assemblyBinding> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Threading.Channels" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" /> <bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" /> </dependentAssembly> </assemblyBinding> </runtime> </configuration>
我的.NET 4.8主应用通过MEF动态加载该类库(如MyLibrary.dll)时,忽略了类库旁的MyLibrary.dll.config中的binding redirect,导致程序集加载失败并抛出CompositionException:
Unhandled Exception: System.ComponentModel.Composition.CompositionException: The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information. 1) Could not load file or assembly 'Microsoft.Bcl.AsyncInterfaces, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) Resulting in: An exception occurred while trying to create an instance of type 'MyLibrary.MyType'. Resulting in: Cannot activate part 'MyLibrary.MyType'. Element: MyLibrary.MyType --> MyLibrary.MyType --> AssemblyCatalog (Assembly="MyLibrary, Version=3.9.0.0, Culture=neutral, PublicKeyToken=null") Resulting in: Cannot get export 'MyLibrary.MyType (ContractName="LibraryBaseType")' from part 'MyLibrary.MyType'. Element: MyLibrary.MyType (ContractName="LibraryBaseType") --> MyLibrary.MyType --> AssemblyCatalog (Assembly="MyLibrary, Version=3.9.0.0, Culture=neutral, PublicKeyToken=null") at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition) at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(CatalogPart part, ExportDefinition export, Boolean isSharedPart) at System.ComponentModel.Composition.Primitives.Export.get_Value() at System.ComponentModel.Composition.ExportServices.GetCastedExportedValue[T](Export export) at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at MainApplication.Start(String[] args) in ...\MainApplication.cs:line 65 at MainApplication.Main(String[] args) in ...\MainApplication.cs:line 31
请问是否有办法让主应用识别动态加载类库旁的binding redirect?还是只能将所有binding redirect复制到主应用的app.config中?
方法1:合并配置到主应用app.config(最直接可靠)
这是.NET Framework下最稳妥的方案,直接把插件MyLibrary.dll.config里<runtime>节点下的所有<assemblyBinding>配置,复制到主应用app.config对应的<runtime>节点中。主应用启动时会加载这些全局重定向规则,MEF加载插件时就能自动应用。
方法2:手动加载插件配置并合并到主应用
可以在加载MEF插件前,通过代码读取插件的配置文件,将其中的binding redirect合并到当前应用的配置中:
using System.Configuration; using System.Xml; using System.IO; using System.Reflection; // 先获取插件所在目录 string pluginAssemblyPath = typeof(MyLibrary.MyType).Assembly.Location; string pluginConfigPath = Path.Combine(Path.GetDirectoryName(pluginAssemblyPath), "MyLibrary.dll.config"); // 加载插件配置 ExeConfigurationFileMap configMap = new ExeConfigurationFileMap { ExeConfigFilename = pluginConfigPath }; Configuration pluginConfig = ConfigurationManager.OpenMappedExeConfiguration(configMap, ConfigurationUserLevel.None); // 获取插件的runtime配置节点 ConfigurationSection pluginRuntimeSection = pluginConfig.GetSection("runtime"); if (pluginRuntimeSection != null) { XmlDocument pluginXml = new XmlDocument(); pluginXml.LoadXml(pluginRuntimeSection.SectionInformation.GetRawXml()); // 获取主应用的runtime配置节点 Configuration mainConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); ConfigurationSection mainRuntimeSection = mainConfig.GetSection("runtime"); if (mainRuntimeSection != null) { XmlDocument mainXml = new XmlDocument(); mainXml.LoadXml(mainRuntimeSection.SectionInformation.GetRawXml()); // 合并插件的assemblyBinding节点到主应用配置 foreach (XmlNode assemblyBindingNode in pluginXml.SelectNodes("//assemblyBinding")) { XmlNode importedNode = mainXml.ImportNode(assemblyBindingNode, true); mainXml.DocumentElement.AppendChild(importedNode); } // 更新主应用配置并刷新 mainRuntimeSection.SectionInformation.SetRawXml(mainXml.OuterXml); mainConfig.Save(ConfigurationSaveMode.Modified); ConfigurationManager.RefreshSection("runtime"); } }
注意:这段代码必须在初始化MEF目录/程序集之前执行,确保重定向规则提前生效。
方法3:通过AssemblyResolve事件手动处理加载
如果不想修改配置文件,可以订阅AppDomain.CurrentDomain.AssemblyResolve事件,在程序集加载失败时手动指定正确版本的依赖路径:
using System.Reflection; using System.IO; AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { AssemblyName requestedAssembly = new AssemblyName(args.Name); string pluginDir = Path.GetDirectoryName(typeof(MyLibrary.MyType).Assembly.Location); // 针对每个需要重定向的依赖单独处理 switch (requestedAssembly.Name) { case "Microsoft.Bcl.AsyncInterfaces": return Assembly.LoadFrom(Path.Combine(pluginDir, "Microsoft.Bcl.AsyncInterfaces.dll")); case "System.Buffers": return Assembly.LoadFrom(Path.Combine(pluginDir, "System.Buffers.dll")); case "System.Memory": return Assembly.LoadFrom(Path.Combine(pluginDir, "System.Memory.dll")); // 其他依赖依次添加 default: return null; } };
这种方式需要逐个处理每个依赖,适合依赖数量较少的场景,灵活性高但维护成本也相对较高。
内容的提问来源于stack exchange,提问作者Brian Henry

