我有以下问题:
我们有一个加载模块的应用程序(添加)。这些模块可能需要app.config中的条目(例如WCF配置)。因为模块是动态加载的,所以我不想在我的应用程序的app.config文件中包含这些条目
我想做的是以下几点:
注意:我不想覆盖默认的app.config!
它应该透明地工作,以便例如ConfigurationManager.AppSettings
使用该新文件。
在评估此问题时,我想出了与此处提供的解决方案相同的解决方案:Reload app.config with nunit 不幸的是,它似乎没有做任何事情,因为我仍然从正常的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);
}
它打印相同的值twices,尽管combinedConfig
包含除普通app.config之外的其他值。
答案 0 :(得分:263)
如果在第一次使用配置系统之前使用链接问题,则hack会有效。在那之后,它不再起作用。
原因:
存在一个缓存路径的类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)
放在应用程序开头的某个位置即可。
答案 1 :(得分:10)
您可以尝试在运行时使用Configuration和添加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
答案 2 :(得分:4)
@Daniel解决方案正常工作。
c-sharp corner.中提供了更多解释的类似解决方案
为了完整性,我想分享我的版本: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;
}
答案 3 :(得分:2)
Daniel的解决方案似乎对下游组件也有效 我之前使用过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;
}
答案 4 :(得分:2)
如果有人感兴趣,这是一种适用于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);
答案 5 :(得分:1)
如果您的配置文件只是在“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的集合。
答案 6 :(得分:1)
精彩的讨论,我在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;
}
答案 7 :(得分:0)
丹尼尔,如果可能的话,尝试使用其他配置机制。我们已经通过这条路线,根据环境/配置文件/组,我们有不同的静态/动态配置文件,最后它变得非常混乱。
您可以尝试某种Profile WebService,您只需从客户端指定一个Web服务URL,并根据客户端的详细信息(您可能具有组/用户级别覆盖),它会加载所需的所有配置。我们还使用了MS Enterprise Library作为其中的一部分。
那就是你不用你的客户端部署配置,你可以与客户分开管理