런타임시 기본 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)
시작시 어딘가에 사용하지 않고 두십시오.
당신이 사용하려고 할 수 있습니다 구성 하고 추가 없는 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 솔루션은 정상적으로 작동합니다. 자세한 설명과 비슷한 솔루션이 c-sharp corner에 있습니다. 완전성을 위해 내 버전을 with과 공유하고 싶습니다 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;
}
Daniel의 솔루션은 이전에 AppDomain.SetData를 사용했던 다운 스트림 어셈블리에서도 작동하는 것처럼 보이지만 내부 구성 플래그를 재설정하는 방법을 알지 못했습니다.
Converted to C++/CLI for those interested
/// <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;
}
If anybody is interested, here is a method that works on 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);
If your config file is just written with key/values in "appSettings", then you can read another file with such code :
System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = configFilePath;
System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings");
Then you can read section.Settings as collection of KeyValueConfigurationElement.
Wonderful discussion, I've adding more comments to ResetConfigMechanism method to understand the magic behind the statement/calls in the method. Also added file path exist check
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;
}
Daniel, if possible try to use other config mechanisms. We have been through this route where we had different static/dynamic config files depending on environment/profile/group and it became quite messy at the end.
you could try out some sort of Profile WebService where you only specify one Web Service URL from the client and depending on Client's details (you might have Group/User level overrides), it loads up all the config it needs. We have also used MS Enterprise Library for some part of it.
that was you dont deploy config with your client and you can manage it separately from your clients
참고URL : https://stackoverflow.com/questions/6150644/change-default-app-config-at-runtime
'programing tip' 카테고리의 다른 글
스칼라에서 배열과리스트의 차이점 (0) | 2020.07.04 |
---|---|
표에서 1을 선택한다는 것은 무슨 의미입니까? (0) | 2020.07.04 |
Swift에서 NS_OPTIONS 스타일 비트 마스크 열거를 만드는 방법은 무엇입니까? (0) | 2020.07.04 |
NoMethodError : 레이크 11로 업그레이드 한 후 정의되지 않은 메소드 'last_comment' (0) | 2020.07.04 |
ASP.NET 세션 쿠키에서 보안 플래그를 설정하는 방법 (0) | 2020.07.04 |