런타임시 기본 app.config 변경


130

다음과 같은 문제가 있습니다.
모듈을로드하는 응용 프로그램이 있습니다 (애드온). 이러한 모듈에는 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 이외의 다른 값을 포함 하지만 동일한 값을 두 번 인쇄합니다 .


AppDomain적절한 구성 파일과 별도로 모듈을 호스팅하는 것은 옵션이 아닙니까?
João Angelo

실제로 애플리케이션이 모듈과 크게 상호 작용하기 때문에 Cross-AppDomain 호출이 많이 발생하기 때문에 실제로는 아닙니다.
Daniel Hilgarth

새 모듈을로드해야 할 때 응용 프로그램을 다시 시작하는 방법은 무엇입니까?
João Angelo

비즈니스 요구 사항과 함께 작동하지 않습니다. 또한 사용자에게는 권한이 없으므로 app.config를 덮어 쓸 수 없습니다.
Daniel Hilgarth

프로그램 파일이 아닌 다른 App.config를로드하기 위해 다시로드합니다. Reload app.config with nunit구성을로드하기 전에 응용 프로그램 항목에 사용하면 해킹이 확실하지 않을 수 있습니다.
João Angelo

답변:


280

링크 된 질문의 해킹은 구성 시스템을 처음 사용하기 전에 사용하면 작동합니다. 그 후에는 더 이상 작동하지 않습니다.
이유 : 경로를 캐시
하는 클래스가 있습니다 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)시작시 어딘가에 사용하지 않고 두십시오.


4
이것은 정말, 정말 훌륭합니다. 이것을 게시 해 주셔서 감사합니다.
user981225

3
@Daniel 대단했습니다 .ApplicationSettingsBase의 확장 메소드로 작업하여 Settings.Default.RedirectAppConfig (path)를 호출 할 수 있습니다. 내가 할 수 있다면 나는 당신에게 +2를 줄 것이다!
JMarsch

2
@ PhilWhittington : 그게 내가 말하는 것입니다.
Daniel Hilgarth

2
종료점을 선언하지 않은 경우 종료점을 억제해야 할 이유가 있습니까?
Gusdor

3
그러나 리플렉션을 사용하여 개인 필드에 액세스하는 것은 현재 작동 할 수 있지만 지원되지 않으며 향후 버전의 .NET Framework에서 중단 될 수 있다는 경고를 사용할 수 있습니다.

10

당신이 사용하려고 할 수 있습니다 구성 하고 추가 없는 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

이것이 어떻게 도움이되는지 모르겠습니다. 로 지정된 파일에 섹션이 추가됩니다 file_path. 이 사용자에 대한 섹션을 사용할 수있게되지 않습니다 ConfigurationManager.GetSection때문에, GetSection사용하는 기본의 app.config.
Daniel Hilgarth

기존 app.config에 섹션을 추가 할 수 있습니다. 방금 이것을 시도-저를 위해 작동
Stecya

내 질문에서 인용 : "참고 : 기본 app.config를 덮어 쓰고 싶지 않습니다!"
Daniel Hilgarth

5
뭐가 문제 야? 단순 : 프로그램이 % ProgramFiles %에 설치되어 있고 사용자가 관리자가 아니기 때문에 사용자는 프로그램을 덮어 쓸 권리가 없습니다.
Daniel Hilgarth

2
@Stecya : 노력해 주셔서 감사합니다. 그러나 문제에 대한 실제 해결책에 대한 내 대답을 참조하십시오.
Daniel Hilgarth

5

@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;
    }

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);

3

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;
}

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의 컬렉션으로 읽을 수 있습니다.


1
이미 말했듯이, ConfigurationManager.GetSection내가 만든 새 파일 을 읽고 싶습니다 . 귀하의 솔루션은 그렇게하지 않습니다.
Daniel Hilgarth

@ 다니엘 : 왜? "configFilePath"에 파일을 지정할 수 있습니다. 따라서 새로 만든 파일의 위치를 ​​알아야합니다. 내가 뭐 놓친 거 없니 ? 또는 실제로 "ConfigurationManager.GetSection"을 사용해야합니다.
Ron

1
그렇습니다 ConfigurationManager.GetSection. 기본 app.config를 사용하십시오. 로 연 구성 파일은 신경 쓰지 않습니다 OpenMappedExeConfiguration.
Daniel Hilgarth

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;
}

0

Daniel, 가능하면 다른 구성 메커니즘을 사용하십시오. 우리는 환경 / 프로파일 / 그룹에 따라 다른 정적 / 동적 구성 파일을 가진이 경로를 겪었으며 결국 매우 지저분 해졌습니다.

클라이언트에서 하나의 웹 서비스 URL 만 지정하는 일종의 Profile WebService를 사용해 볼 수 있으며 클라이언트의 세부 정보 (그룹 / 사용자 수준 재정의가있을 수 있음)에 따라 필요한 모든 구성을로드합니다. 또한 MS Enterprise Library를 일부 사용했습니다.

클라이언트와 함께 구성을 배포하지 않았으며 클라이언트와 별도로 구성을 관리 할 수있었습니다.


3
답변 주셔서 감사합니다. 그러나 이것의 전체 이유는 구성 파일의 배송을 피하기 위해서입니다. 모듈의 구성 세부 사항은 데이터베이스에서로드됩니다. 그러나 모듈 개발자에게 기본 .NET 구성 메커니즘의 편안함을 제공하기 위해 런타임에 해당 모듈 구성을 하나의 구성 파일에 통합하고 이것을 기본 구성 파일로 만들고 싶습니다. 이유는 간단합니다. app.config를 통해 구성 할 수있는 라이브러리가 많이 있습니다 (예 : WCF, EntLib, EF 등). 다른 구성 메커니즘을 소개하면 구성이 계속됩니다 (계속)
Daniel Hilgarth
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.