Change default app.config at runtime
我有以下问题:我们有一个加载模块(插件)的应用程序。这些模块可能需要app.config中的条目(例如wcf配置)。因为模块是动态加载的,所以我不希望在应用程序的app.config文件中包含这些条目。我想做的是:
- 在内存中创建一个新的app.config,其中包含模块中的config部分
- 告诉我的应用程序使用新的app.config
注意:我不想覆盖默认app.config!
它应该透明地工作,例如
在我对这个问题的评估中,我提出了与这里提供的相同的解决方案:用nunit重新加载app.config。不幸的是,它似乎没有任何作用,因为我仍然从普通的app.config获取数据。
我用这个代码来测试它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } |
它会输出相同的值twices,尽管
如果在第一次使用配置系统之前使用了链接问题中的hack,那么它将起作用。在那之后,它就不再起作用了。原因:存在一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | 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); } } } |
用法如下:
1 2 3 4 5 6 | // 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,只需将
您可以尝试在运行时使用配置和添加配置节
1 2 3 4 5 6 7 | Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration( new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config, ConfigurationUserLevel.None ); applicationConfiguration.Sections.Add("section",new YourSection()) applicationConfiguration.Save(ConfigurationSaveMode.Full,true); |
编辑:这里是基于反射的解决方案(不过不是很好)
创建从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 } |
然后通过反射将其设置为
@丹尼尔解决方案工作正常。另一个解释更多的类似解决方案是C-Sharp角。为了完整起见,我想分享我的版本:与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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; } |
如果有人感兴趣,这里有一个方法可以在Mono上工作。
1 2 3 4 5 6 | 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); |
丹尼尔的解决方案似乎也适用于下游装配。我以前使用过appdomain.setdata,但不知道如何重置内部配置标志。
转换为C++CLI为那些感兴趣的人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /// <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; } |
精彩的讨论,我已经为resetconfigmechanism方法添加了更多的注释,以了解方法中语句/调用背后的魔力。还添加了文件路径存在检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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; } |
如果您的配置文件只是用"appsettings"中的键/值写入的,那么您可以使用以下代码读取另一个文件:
1 2 3 4 5 | 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的集合读取。
Daniel,如果可能,尝试使用其他配置机制。我们已经通过了这条路径,根据环境/profile/group,我们有不同的静态/动态配置文件,最后变得非常混乱。
您可以尝试使用某种类型的profile web service,其中您只指定来自客户端的一个Web服务URL,并且根据客户端的详细信息(您可能具有组/用户级覆盖),它将加载所需的所有配置。我们还使用了MS Enterprise库作为其中的一部分。
那就是你不需要在你的客户机上部署配置,你可以和客户机分开管理它。