Change default app.config at runtime

后端 未结 8 1977
伪装坚强ぢ
伪装坚强ぢ 2020-11-22 05:50

I have the following problem:
We have an application that loads modules (add ons). These modules might need entries in the app.config (e.g. WCF configuration). Because t

相关标签:
8条回答
  • 2020-11-22 06:31

    You can try to use Configuration and Add ConfigurationSection on runtime

    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: Here is solution based on reflection (not very nice though)

    Create class derived from 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
    }
    

    then via reflection set it to private field in 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 讨论(0)
  • 2020-11-22 06:31

    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

    0 讨论(0)
  • 2020-11-22 06:32

    Daniel's solution seems to work even for downstream assemblies I had used AppDomain.SetData before, but was unaware of how to reset the internal configuration flags

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 06:36

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 06:43

    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);
    
    0 讨论(0)
  • 2020-11-22 06:45

    The hack in the linked question works if it is used before the configuration system is used the first time. After that, it doesn't work any more.
    The reason:
    There exists a class ClientConfigPaths that caches the paths. So, even after changing the path with SetData, it is not re-read, because there already exist cached values. The solution is to remove these, too:

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

    Usage is like this:

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

    If you want to change the used app.config for the whole runtime of your application, simply put AppConfig.Change(tempFileName) without the using somewhere at the start of your application.

    0 讨论(0)
提交回复
热议问题