在运行时更改默认 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 以外的其他值。

87058 次浏览

您可以尝试在运行时使用 配置和 Add配置组

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,如果可能的话,尝试使用其他配置机制。我们已经通过这条路线,我们有不同的静态/动态配置文件取决于环境/配置文件/组,最后变得相当混乱。

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

也就是说,你不需要部署客户端的配置,你可以把它和客户端分开来管理

如果您的配置文件只是用“ 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 的集合读取。

如果在第一次使用配置系统之前使用链接问题中的黑客技术,那么它就可以工作。之后就没用了。
原因是:
存在一个缓存路径的类 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)而不使用它。

@ 丹尼尔解决方案行得通。 在 升 C 角。中也有一个类似的解决方案,对其进行了更多的解释 为了完整起见,我想分享我的版本: 使用 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);

精彩的讨论,我已经向 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;
}