Alterar o app.config padrão no tempo de execução

Eu tenho o seguinte problema:
Nós temos um aplicativo que carrega módulos (add ons). Esses módulos podem precisar de inputs no app.config (por exemplo, configuração do WCF). Como os módulos são carregados dinamicamente, não quero ter essas inputs no arquivo app.config do meu aplicativo.
O que eu gostaria de fazer é o seguinte:

  • Crie um novo app.config na memory que incorpore as seções de configuração dos módulos
  • Diga ao meu aplicativo para usar esse novo app.config

Nota: Eu não quero sobrescrever o app.config padrão!

Ele deve funcionar de forma transparente, para que, por exemplo, o ConfigurationManager.AppSettings use esse novo arquivo.

Durante minha avaliação deste problema, criei a mesma solução fornecida aqui: Recarregar app.config com nunit .
Infelizmente, parece não fazer nada, porque ainda recebo os dados do app.config normal.

Eu usei este código para testá-lo:

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

Ele imprime os mesmos valores twices, embora combinedConfig contenha outros valores além do app.config normal.

O hack da questão vinculada funciona se for usado antes que o sistema de configuração seja usado pela primeira vez. Depois disso, não funciona mais.
O motivo:
Existe uma class ClientConfigPaths que armazena os caminhos em cache. Portanto, mesmo depois de alterar o caminho com SetData , ele não é SetData , pois já existem valores armazenados em cache. A solução é removê-los também:

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

O uso é assim:

 // the default app.config is used. using(AppConfig.Change(tempFileName)) { // the app.config in tempFileName is used } // the default app.config is used. 

Se você quiser alterar o app.config usado para todo o tempo de execução do seu aplicativo, basta colocar AppConfig.Change(tempFileName) sem usar algum lugar no início do seu aplicativo.

Você pode tentar usar configuração e adicionar ConfigurationSection em tempo de execução

 Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration( new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config, ConfigurationUserLevel.None ); applicationConfiguration.Sections.Add("section",new YourSection()) applicationConfiguration.Save(ConfigurationSaveMode.Full,true); 

EDIT: aqui é a solução com base na reflection (embora não seja muito bom)

Criar class derivada do 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 } 

em seguida, por meio de reflection, defina-o para o campo privado no 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 

A solução @Daniel funciona bem. Uma solução semelhante com mais explicação está no canto c-afiado. Para completar, gostaria de compartilhar minha versão: com o using e os sinalizadores de bit abreviados.

 using System;//AppDomain using System.Linq;//Where using System.Configuration;//app.config using System.Reflection;//BindingFlags ///  /// Use your own App.Config file instead of the default. ///  ///  public static void ChangeAppConfig(string NewAppConfigFullPathName) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } ///  /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. ///  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; } 

Solução de Daniel parece funcionar mesmo para assemblies downstream Eu tinha usado AppDomain.SetData antes, mas não sabia como redefinir os sinalizadores de configuração interna

Convertido para C ++ / CLI para os interessados

 ///  /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. ///  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; } ///  /// Use your own App.Config file instead of the default. ///  ///  void ChangeAppConfig(String ^NewAppConfigFullPathName) { AppDomain::CurrentDomain->SetData(L"APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } 

Se alguém estiver interessado, aqui está um método que funciona no 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); 

Se o seu arquivo de configuração acabou de ser escrito com chave / valores em “appSettings”, então você pode ler outro arquivo com esse código:

 System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); configFileMap.ExeConfigFilename = configFilePath; System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings"); 

Então você pode ler section.Settings como coleção de KeyValueConfigurationElement.

Discussão maravilhosa, adicionei mais comentários ao método ResetConfigMechanism para entender a mágica por trás da instrução / chamadas no método. Também foi adicionado o caminho do arquivo

 using System;//AppDomain using System.Linq;//Where using System.Configuration;//app.config using System.Reflection;//BindingFlags using System.Io; ///  /// Use your own App.Config file instead of the default. ///  ///  public static void ChangeAppConfig(string NewAppConfigFullPathName) { if(File.Exists(NewAppConfigFullPathName) { AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", NewAppConfigFullPathName); ResetConfigMechanism(); return; } } ///  /// Remove cached values from ClientConfigPaths. /// Call this after changing path to App.Config. ///  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, se possível, tente usar outros mecanismos de configuração. Nós passamos por essa rota onde nós tínhamos diferentes arquivos de configuração estáticos / dynamics, dependendo do ambiente / perfil / grupo, e isso se tornou bastante confuso no final.

você pode experimentar algum tipo de WebService de perfil, em que você especifica apenas uma URL de serviço da Web do cliente e, dependendo dos detalhes do cliente (você pode ter substituições no nível de grupo / usuário), carrega todas as configurações necessárias. Também usamos a MS Enterprise Library em parte dela.

você não implantou a configuração com seu cliente e pode gerenciá-lo separadamente de seus clientes