在运行时更改默认的app.config

我有以下问题:
我们有一个加载模块的应用程序(附加)。 这些模块可能需要app.config中的条目(例如WCF配置)。 因为模块是动态加载的,所以我不想在我的应用程序的app.config文件中包含这些条目。
我想要做的是以下几点:

  • 在内存中创建一个新的app.config,该模块包含模块中的配置部分
  • 告诉我的应用程序使用新的app.config

注意:我不想覆盖默认的app.config!

它应该透明地工作,以便例如ConfigurationManager.AppSettings使用该新文件。

在我评估这个问题的过程中,我想出了与此处提供的解决方案相同的方法: 使用nunit重新加载app.config 。
不幸的是,它似乎没有做任何事情,因为我仍然从正常的app.config中获取数据。

我用这个代码来测试它:

 Console.WriteLine(ConfigurationManager.AppSettings["SettingA"]); Console.WriteLine(Settings.Default.Setting); var combinedConfig = string.Format(CONFIG2, CONFIG); var tempFileName = Path.GetTempFileName(); using (var writer = new StreamWriter(tempFileName)) { writer.Write(combinedConfig); } using(AppConfig.Change(tempFileName)) { Console.WriteLine(ConfigurationManager.AppSettings["SettingA"]); Console.WriteLine(Settings.Default.Setting); } 

它会打印相同的值,尽管combinedConfig包含的值不是普通的app.config。

如果在首次使用配置系统之前使用链接问题中的黑客行为。 之后,它不再工作了。
原因:
存在缓存路径的类ClientConfigPaths 。 因此,即使在用SetData更改路径之后,也不会重新读取,因为已经存在缓存的值。 解决办法是也删除这些:

 using System; using System.Configuration; using System.Linq; using System.Reflection; public abstract class AppConfig : IDisposable { public static AppConfig Change(string path) { return new ChangeAppConfig(path); } public abstract void Dispose(); private class ChangeAppConfig : AppConfig { private readonly string oldConfig = AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString(); private bool disposedValue; public ChangeAppConfig(string path) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path); ResetConfigMechanism(); } public override void Dispose() { if (!disposedValue) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", oldConfig); ResetConfigMechanism(); disposedValue = true; } GC.SuppressFinalize(this); } private static void ResetConfigMechanism() { typeof(ConfigurationManager) .GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static) .SetValue(null, 0); typeof(ConfigurationManager) .GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static) .SetValue(null, null); typeof(ConfigurationManager) .Assembly.GetTypes() .Where(x => x.FullName == "System.Configuration.ClientConfigPaths") .First() .GetField("s_current", BindingFlags.NonPublic | BindingFlags.Static) .SetValue(null, null); } } } 

用法如下所示:

 // the default app.config is used. using(AppConfig.Change(tempFileName)) { // the app.config in tempFileName is used } // the default app.config is used. 

如果您想要更改应用程序的整个运行时使用的app.config,只需将AppConfig.Change(tempFileName)放在应用程序的开始位置即可。

您可以尝试在运行时使用Configuration和Add ConfigurationSection

 Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration( new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config, ConfigurationUserLevel.None ); applicationConfiguration.Sections.Add("section",new YourSection()) applicationConfiguration.Save(ConfigurationSaveMode.Full,true); 

编辑:这是基于反思的解决方案(虽然不是很好)

创建从IInternalConfigSystem派生的类

 public class ConfigeSystem: IInternalConfigSystem { public NameValueCollection Settings = new NameValueCollection(); #region Implementation of IInternalConfigSystem public object GetSection(string configKey) { return Settings; } public void RefreshConfig(string sectionName) { //throw new NotImplementedException(); } public bool SupportsUserConfig { get; private set; } #endregion } 

然后通过反射将其设置为ConfigurationManager专用字段

  ConfigeSystem configSystem = new ConfigeSystem(); configSystem.Settings.Add("s1","S"); Type type = typeof(ConfigurationManager); FieldInfo info = type.GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static); info.SetValue(null, configSystem); bool res = ConfigurationManager.AppSettings["s1"] == "S"; // return true 

@Daniel解决方案可以。 有更多解释的类似解决方案是在尖锐的角落。 为了完整性,我想分享我的版本: using ,位标志缩写。

 using System;//AppDomain using System.Linq;//Where using System.Configuration;//app.config using System.Reflection;//BindingFlags /// <summary> /// Use your own App.Config file instead of the default. /// </summary> /// <param name="NewAppConfigFullPathName"></param> public static void ChangeAppConfig(string NewAppConfigFullPathName) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } /// <summary> /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. /// </summary> private static void ResetConfigMechanism() { BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Static; typeof(ConfigurationManager) .GetField("s_initState", Flags) .SetValue(null, 0); typeof(ConfigurationManager) .GetField("s_configSystem", Flags) .SetValue(null, null); typeof(ConfigurationManager) .Assembly.GetTypes() .Where(x => x.FullName == "System.Configuration.ClientConfigPaths") .First() .GetField("s_current", Flags) .SetValue(null, null); return; } 

丹尼尔的解决方案似乎工作,即使是我以前使用AppDomain.SetData的下游程序集,但不知道如何重置内部配置标志

感兴趣的人转换为C ++ / CLI

 /// <summary> /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. /// </summary> void ResetConfigMechanism() { BindingFlags Flags = BindingFlags::NonPublic | BindingFlags::Static; Type ^cfgType = ConfigurationManager::typeid; Int32 ^zero = gcnew Int32(0); cfgType->GetField("s_initState", Flags) ->SetValue(nullptr, zero); cfgType->GetField("s_configSystem", Flags) ->SetValue(nullptr, nullptr); for each(System::Type ^t in cfgType->Assembly->GetTypes()) { if (t->FullName == "System.Configuration.ClientConfigPaths") { t->GetField("s_current", Flags)->SetValue(nullptr, nullptr); } } return; } /// <summary> /// Use your own App.Config file instead of the default. /// </summary> /// <param name="NewAppConfigFullPathName"></param> void ChangeAppConfig(String ^NewAppConfigFullPathName) { AppDomain::CurrentDomain->SetData(L"APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } 

如果有人有兴趣,这是一个在Mono上工作的方法。

 string configFilePath = ".../App"; System.Configuration.Configuration newConfiguration = ConfigurationManager.OpenExeConfiguration(configFilePath); FieldInfo configSystemField = typeof(ConfigurationManager).GetField("configSystem", BindingFlags.NonPublic | BindingFlags.Static); object configSystem = configSystemField.GetValue(null); FieldInfo cfgField = configSystem.GetType().GetField("cfg", BindingFlags.Instance | BindingFlags.NonPublic); cfgField.SetValue(configSystem, newConfiguration); 

如果你的配置文件只是用“appSettings”中的键/值写的,那么你可以用这样的代码读取另一个文件:

 System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); configFileMap.ExeConfigFilename = configFilePath; System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings"); 

然后你可以读取section.Settings作为KeyValueConfigurationElement的集合。

精彩的讨论,我已经添加更多的评论ResetConfigMechanism方法来理解背后的声明/调用方法。 还添加了文件路径存在检查

 using System;//AppDomain using System.Linq;//Where using System.Configuration;//app.config using System.Reflection;//BindingFlags using System.Io; /// <summary> /// Use your own App.Config file instead of the default. /// </summary> /// <param name="NewAppConfigFullPathName"></param> public static void ChangeAppConfig(string NewAppConfigFullPathName) { if(File.Exists(NewAppConfigFullPathName) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } } /// <summary> /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. /// </summary> private static void ResetConfigMechanism() { BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Static; /* s_initState holds one of the four internal configuration state. 0 - Not Started, 1 - Started, 2 - Usable, 3- Complete Setting to 0 indicates the configuration is not started, this will hint the AppDomain to reaload the most recent config file set thru .SetData call More [here][1] */ typeof(ConfigurationManager) .GetField("s_initState", Flags) .SetValue(null, 0); /*s_configSystem holds the configuration section, this needs to be set as null to enable reload*/ typeof(ConfigurationManager) .GetField("s_configSystem", Flags) .SetValue(null, null); /*s_current holds the cached configuration file path, this needs to be made null to fetch the latest file from the path provided */ typeof(ConfigurationManager) .Assembly.GetTypes() .Where(x => x.FullName == "System.Configuration.ClientConfigPaths") .First() .GetField("s_current", Flags) .SetValue(null, null); return; } 

丹尼尔,如果可能的话尝试使用其他配置机制。 我们已经通过这个路线,根据环境/配置文件/组,我们有不同的静态/动态配置文件,最后变得相当混乱。

您可以尝试某种Profile WebService,您只能从客户端指定一个Web Service URL,并根据客户端的详细信息(可能有Group / User级别覆盖)加载所需的所有配置。 我们也使用微软企业库的一部分。

那就是你不用你的客户部署配置,你可以从你的客户单独管理它