我们有一个加载模块(附加组件)的应用程序。这些模块可能需要在app.config中输入(例如WCF配置)。由于模块是动态加载的,因此我不想在应用程序的app.config文件中包含这些条目。
我想做的是以下事情:
在内存中创建一个新的app.config,其中包含模块中的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之外的其他值。#1 楼
如果在首次使用配置系统之前就使用了链接问题中的技巧,则该技巧是可行的。之后,它不再起作用。原因:
存在一个类
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)
放在应用程序的开始位置即可,而不用在其他位置。评论
这真的非常非常好。非常感谢您发布此信息。
–user981225
2012年11月12日16:57
@Daniel太棒了-我将其用于ApplicationSettingsBase的扩展方法中,以便可以调用Settings.Default.RedirectAppConfig(path)。如果可以的话,我会给你+2!
– JMarsch
13年2月14日在17:50
@PhilWhittington:那是我的意思,是的。
–丹尼尔·希尔加斯(Daniel Hilgarth)
13年8月6日在12:33
出于兴趣,是否有任何理由压制终结器?没有声明终结器?
– Gusdor
2014年4月1日上午10:19
除此之外,现在可以使用反射来访问私有字段,但是它可能会使用警告,提示它不受支持,并且可能会在.NET Framework的未来版本中中断。
–user743382
15年3月17日在9:36
#2 楼
您可以尝试在运行时使用Configuration并添加ConfigurationSectionConfiguration 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
评论
我看不出这对我有什么帮助。这会将节添加到file_path指定的文件中。这将不会使该部分对ConfigurationManager.GetSection的用户可用,因为GetSection使用默认的app.config。
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日10:13
您可以将Sections添加到现有的app.config中。刚刚尝试过-对我有用
– Stecya
2011年5月27日晚上10:24
引用我的问题:“注意:我不想覆盖默认的app.config!”
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上10:26
怎么了?简单:用户无权覆盖它,因为该程序安装在%ProgramFiles%中,并且该用户不是管理员。
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上10:33
@Stecya:谢谢您的努力。但是,请参阅我的答案以真正解决该问题。
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上11:35
#3 楼
@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;
}
#4 楼
如果有人感兴趣,这是适用于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 楼
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;
}
#6 楼
如果您的配置文件只是在“ 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的集合。
评论
正如我已经说过的,我想让ConfigurationManager.GetSection读取我创建的新文件。您的解决方案无法做到这一点。
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上10:42
@Daniel:为什么?您可以在“ configFilePath”中指定任何文件。因此,您只需要知道新创建文件的位置即可。我错过了什么 ?还是您真的需要使用“ ConfigurationManager.GetSection”而不是其他任何东西?
–罗恩
2011年5月27日10:43
是的,您确实错过了一些事情:ConfigurationManager.GetSection使用默认的app.config。它与您使用OpenMappedExeConfiguration打开的配置文件无关。
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上10:45
#7 楼
精彩的讨论,我在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;
}
#8 楼
Daniel,如果可能,请尝试使用其他配置机制。我们经历过这条路,根据环境/配置文件/组,我们有不同的静态/动态配置文件,最后变得非常混乱。您可以尝试某种Profile WebService,其中您仅从客户端指定一个Web Service URL,然后根据客户端的详细信息(您可能具有组/用户级别的替代),它将加载所有配置它需要。我们还使用了MS Enterprise Library的一部分。
那是您不与客户端一起部署配置,并且可以与客户端分开进行管理
评论
感谢您的回答。但是,这样做的全部原因是避免运送配置文件。模块的配置详细信息是从数据库中加载的。但是,因为我想让模块开发人员轻松使用默认的.NET配置机制,所以我想在运行时将这些模块配置合并到一个配置文件中,并将其设置为默认配置文件。原因很简单:存在许多可以通过app.config进行配置的库(例如WCF,EntLib,EF等)。如果我要介绍另一种配置机制,则配置将(续)
–丹尼尔·希尔加斯(Daniel Hilgarth)
2011年5月27日晚上10:22
评论
无法将模块与相应的配置文件托管在单独的AppDomain中吗?并非如此,因为这会导致很多Cross-AppDomain调用,因为应用程序与模块之间的交互非常频繁。
当需要加载新模块时,应用程序如何重启?
这不能与业务需求一起使用。此外,我无法覆盖app.config,因为用户无权这样做。
您将需要重新加载以加载其他App.config,而不是加载程序文件中的那个。不确定是否可以在加载任何配置之前在应用程序条目上使用带有nunit的Reload app.config中的hack。