通过CSI.EXE运行脚本加载自定义app.config<section>遇System.Configuration异常
解决CSI.exe中ConfigurationManager无法加载脚本目录下自定义配置节程序集的问题
问题背景
我有程序集X,它依赖包含自定义<section>的app.config文件,在常规宿主应用中引用X时一切正常。app.config内容如下:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="Foo" type="Foo.Common.FooConfigurationSectionHandler, Foo.Common" /> </configSections> <Foo xmlns="http://mycompany.com/foo/configuration"> <property name="Foo.Server.ConnectionString" value="zipzap" /> </Foo> </configuration>
现在通过C#脚本(csi.exe)引用X,脚本目录下有Foo.Common.dll,脚本中可以正常引用并使用该程序集的类,但当执行加载上述app.config的代码时,ConfigurationManager抛出错误,无法找到Foo.Common程序集。错误信息如下:
An error occurred creating the configuration section handler for Foo: Could not load file or assembly 'Foo.Common' or one of its dependencies. The system cannot find the file specified. (C:\stuff\desk\script\App.config line 4) at System.Configuration.BaseConfigurationRecord.FindAndEnsureFactoryRecord(String configKey, Boolean& isRootDeclaredHere) at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject) at System.Configuration.BaseConfigurationRecord.GetSection(String configKey) at System.Configuration.ConfigurationManager.GetSection(String sectionName)
测试发现将DLL放到csi.exe目录下可以正常运行,但实际场景中存在程序集冲突,无法这么做,需要解决办法。
解决方案
方法1:注册程序集解析事件
在脚本开头注册AppDomain.CurrentDomain.AssemblyResolve事件,当ConfigurationManager找不到程序集时,从脚本所在目录加载目标程序集。这种方法灵活性高,无需修改app.config:
#r "Foo.Common.dll" #r "System.Configuration.dll" using System; using System.Configuration; using System.Reflection; using Foo.Common; // 注册程序集解析事件,找不到程序集时从脚本目录加载 AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { var assemblyName = new AssemblyName(args.Name).Name; var scriptDir = AppDomain.CurrentDomain.BaseDirectory; var assemblyPath = System.IO.Path.Combine(scriptDir, $"{assemblyName}.dll"); if (System.IO.File.Exists(assemblyPath)) { return Assembly.LoadFrom(assemblyPath); } return null; // 返回null让系统继续执行默认查找逻辑 }; // 加载并使用配置节 var fooSection = ConfigurationManager.GetSection("Foo") as FooConfigurationSectionHandler; // 后续业务代码...
方法2:修改app.config添加程序集探测路径
在app.config的<configuration>节点下添加<runtime>配置,指定脚本目录作为程序集探测路径。注意如果运行csi时的工作目录不是脚本所在目录,需要使用绝对路径:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="Foo" type="Foo.Common.FooConfigurationSectionHandler, Foo.Common" /> </configSections> <!-- 添加程序集探测配置 --> <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <!-- privatePath可填相对路径(基于csi运行工作目录)或绝对路径 --> <probing privatePath="C:\stuff\desk\script" /> </assemblyBinding> </runtime> <Foo xmlns="http://mycompany.com/foo/configuration"> <property name="Foo.Server.ConnectionString" value="zipzap" /> </Foo> </configuration>
方法3:返回已加载的程序集
脚本已经通过#r "Foo.Common.dll"加载了程序集,可在解析事件中直接返回该已加载的程序集,避免重复加载:
#r "Foo.Common.dll" #r "System.Configuration.dll" using System; using System.Configuration; using System.Reflection; using Foo.Common; // 预先加载并保存程序集引用 var loadedFooAssembly = Assembly.LoadFrom("Foo.Common.dll"); AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { var requestedAssembly = new AssemblyName(args.Name); if (requestedAssembly.Name == loadedFooAssembly.GetName().Name) { return loadedFooAssembly; } return null; }; // 加载配置节 var fooSection = ConfigurationManager.GetSection("Foo") as FooConfigurationSectionHandler;
内容的提问来源于stack exchange,提问作者Mud
相关产品推荐
相关产品推荐

