134 votes

Modifier le fichier app.config par défaut au moment de l'exécution

J'ai le problème suivant :
Nous avons une application qui charge des modules (add ons). Ces modules peuvent avoir besoin d'entrées dans l'app.config (par exemple, la configuration WCF). Comme les modules sont chargés dynamiquement, je ne veux pas avoir ces entrées dans le fichier app.config de mon application.
Ce que je voudrais faire est le suivant :

  • Créer un nouveau app.config en mémoire qui incorpore les sections config des modules
  • Dites à mon application d'utiliser cette nouvelle app.config.

Note : Je ne veux pas écraser l'app.config par défaut !

Il devrait fonctionner de manière transparente, de sorte que, par exemple ConfigurationManager.AppSettings utilise ce nouveau fichier.

Lors de mon évaluation de ce problème, j'ai trouvé la même solution que celle proposée ici : Recharger app.config avec nunit .
Malheureusement, cela ne semble rien faire, car j'obtiens toujours les données à partir de l'app.config normal.

J'ai utilisé ce code pour le tester :

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

Il imprime les mêmes valeurs deux fois, bien que combinedConfig contient d'autres valeurs que le app.config normal.

0 votes

Héberger les modules dans des AppDomain avec le fichier de configuration approprié n'est pas une option ?

0 votes

Pas vraiment, car cela entraînerait un grand nombre d'appels Cross-AppDomain, car l'application interagit assez fortement avec les modules.

0 votes

Que diriez-vous d'un redémarrage de l'application lorsqu'un nouveau module doit être chargé ?

287voto

Daniel Hilgarth Points 90722

Le hack de la question liée fonctionne s'il est utilisé avant la première utilisation du système de configuration. Après cela, il ne fonctionne plus.
La raison :
Il existe une classe ClientConfigPaths qui met en cache les chemins. Ainsi, même après avoir modifié le chemin avec SetData il n'est pas relu, car il existe déjà des valeurs en cache. La solution consiste à les supprimer également :

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

L'utilisation est comme ça :

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

Si vous voulez changer l'app.config utilisé pour l'ensemble du runtime de votre application, il suffit de mettre AppConfig.Change(tempFileName) sans l'utilisation quelque part au début de votre application.

4 votes

C'est vraiment, vraiment excellent. Merci beaucoup d'avoir posté ça.

0 votes

Daniel, c'est génial ! Merci ! Plus besoin de charger un autre AppDomain !

3 votes

@Daniel C'était génial -- Je l'ai intégré dans une méthode d'extension pour ApplicationSettingsBase, de sorte que je puisse appeler Settings.Default.RedirectAppConfig(path). Je vous donnerais bien +2 si je pouvais !

10voto

Stecya Points 12073

Vous pouvez essayer d'utiliser Configuration et Ajouter ConfigurationSection en cours d'exécution

Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration(
                        new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config,
                        ConfigurationUserLevel.None
                        );

applicationConfiguration.Sections.Add("section",new YourSection())
applicationConfiguration.Save(ConfigurationSaveMode.Full,true);

EDITAR: Voici une solution basée sur la réflexion (pas très belle cependant)

Créer une classe dérivée de 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
}

puis, par réflexion, le définir comme champ privé dans 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

0 votes

Je ne vois pas en quoi cela m'aide. Ceci ajoutera une section au fichier spécifié par file_path . Cela ne rendra pas la section disponible pour les utilisateurs de ConfigurationManager.GetSection parce que GetSection utilise le app.config par défaut.

0 votes

Vous pouvez ajouter des sections à votre app.config existant. Je viens d'essayer ceci - ça marche pour moi

0 votes

Citation de ma question : "Note : je ne veux pas écraser l'app.config par défaut !"

5voto

Roland Points 323

La solution de @Daniel fonctionne bien. Une solution similaire avec plus d'explications se trouve dans un coin pointu. Pour être complet, je voudrais partager ma version : avec using et les drapeaux binaires en abrégé.

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

4voto

LiohAu Points 571

Si cela intéresse quelqu'un, voici une méthode qui fonctionne sur 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);

3voto

Bill Points 29

La solution de Daniel semble fonctionner même pour les assemblages en aval. J'avais déjà utilisé AppDomain.SetData, mais je ne savais pas comment réinitialiser les drapeaux de configuration interne.

Converti en C++/CLI pour ceux qui sont intéressés

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X