How to Update (Add/Modify/Delete) keys in AppSettings section of web.config at runtime
Asked Answered
A

4

7

I like to Update keys/Values defined in AppSettings section of Web.config at runtime. however I DO NOT want to actually save them to Web.config file.

I have a huge web application that have consists of many modules, DLLs and source code files. A bunch of critical information ranged from database configuration, encryption keys, username and passwords for webservices are saved in AppSettings section of the web.config file. Recent project requirement needs me to move these values out of web.config and keep in a secure storage.

I already secured these values in an external location and I can read them back when application starts.

here is the sample code.

Global.asax

public class Global: System.Web.HttpApplication {
    protected void Application_Start(object sender, EventArgs e) {
        Dictionary<string, string> secureConfig = new Dictionary<string,string>{};

        // --------------------------------------------------------------------
        // Here I read and decrypt keys and add them to secureConfig dictionary
        // To test assume the following line is a key stored in secure sotrage.
        //secureConfig = SecureConfig.LoadConfig();
        secureConfig.Add("ACriticalKey","VeryCriticalValue");
        // --------------------------------------------------------------------

        foreach (KeyValuePair<string, string> item in secureConfig) {
            ConfigurationManager.AppSettings.Add(item.Key, item.Value);
        }
    }
}

As you may noticed it is not feasible to change references to AppSettings in a massive code created by multiple programming teams to read their settings from my secureConfig dictionary and on the other hand I should not save these values in web.config file which is available to web administrators and operators, system admins and cloud admins.

To Make programmers life easier, I want to let them add their values to AppSettings section of web.config during development, but they will be removed from there and put to secure storage later during deployment, however these values should be available to program transparently as they are still in AppSettings section.

Question: how can I add values to AppSettings at runtime so program can read them using ConfigurationManager.AppSettings["ACriticalKey"] to get "VeryCriticalValue" without saving them in Web.Config?

Please note: ConfigurationManager.AppSettings.Add(item.Key, item.Value); gives me ConfigurationErrorsException with message The configuration is read only.

Please note: Preferably some settings should be able to stay in AppSettings as before

Alvin answered 27/3, 2013 at 7:22 Comment(1)
I know this question is old, but I wanted to reference an answer I just posted regarding the same issue. https://mcmap.net/q/236964/-how-do-you-modify-the-web-config-appsettings-at-runtimeSyringomyelia
A
0

Thanks to nkvu which directed me to a his first link which in turn sent me to Williarob's post "Override Configuration Manager" I managed to find a solution to my question.

The mentioned blog post covers how to read settings from another XML file and it works with both windowed applications and web applications (with a little modification in config file name and path). Although this blog written on 2010 it is still working fine with .NET4 without problem.

However as I was going to read my configuration from a secure device, I simplified the class and here is how to use the classes provided by Williarob

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;

namespace Williablog.Core.Configuration {

    public sealed class ConfigSystem: IInternalConfigSystem {
        private static IInternalConfigSystem clientConfigSystem;

        private object appsettings;

        private object connectionStrings;

        /// <summary>
        /// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
        /// </summary>
        public static void Install() {
            FieldInfo[] fiStateValues = null;
            Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);

            if (null != tInitState) {
                fiStateValues = tInitState.GetFields();
            }

            FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
            FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);

            if (fiInit != null && fiSystem != null && null != fiStateValues) {
                fiInit.SetValue(null, fiStateValues[1].GetValue(null));
                fiSystem.SetValue(null, null);
            }

            ConfigSystem confSys = new ConfigSystem();
            Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
            IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
            configSettingsFactory.SetConfigurationSystem(confSys, false);

            Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
            clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
        }

        #region IInternalConfigSystem Members
        public object GetSection(string configKey) {
            // get the section from the default location (web.config or app.config)
            object section = clientConfigSystem.GetSection(configKey);

            switch (configKey) {
                case "appSettings":
                    // Return cached version if exists
                    if (this.appsettings != null) {
                        return this.appsettings;
                    }

                    // create a new collection because the underlying collection is read-only
                    var cfg = new NameValueCollection();

                    // If an AppSettings section exists in Web.config, read and add values from it
                    if (section is NameValueCollection) {
                        NameValueCollection localSettings = (NameValueCollection) section;
                        foreach (string key in localSettings) {
                            cfg.Add(key, localSettings[key]);
                        }
                    }

                    // --------------------------------------------------------------------
                    // Here I read and decrypt keys and add them to secureConfig dictionary
                    // To test assume the following line is a key stored in secure sotrage.
                    //secureConfig = SecureConfig.LoadConfig();
                    secureConfig.Add("ACriticalKey", "VeryCriticalValue");
                    // --------------------------------------------------------------------                        
                    foreach (KeyValuePair<string, string> item in secureConfig) {
                        if (cfg.AllKeys.Contains(item.Key)) {
                            cfg[item.Key] = item.Value;
                        } else {
                            cfg.Add(item.Key, item.Value);
                        }
                    }
                    // --------------------------------------------------------------------                        


                    // Cach the settings for future use
                    this.appsettings = cfg;
                    // return the merged version of the items from secure storage and appsettings
                    section = this.appsettings;
                    break;

                case "connectionStrings":
                    // Return cached version if exists
                    if (this.connectionStrings != null) {
                        return this.connectionStrings;
                    }

                    // create a new collection because the underlying collection is read-only
                    ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();

                    // copy the existing connection strings into the new collection
                    foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
                        connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
                    }

                    // --------------------------------------------------------------------
                    // Again Load connection strings from secure storage and merge like below
                    // connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
                    // --------------------------------------------------------------------                        

                    // Cach the settings for future use
                    this.connectionStrings = connectionStringsSection;
                    // return the merged version of the items from secure storage and appsettings
                    section = this.connectionStrings;
                    break;
            }

            return section;
        }

        public void RefreshConfig(string sectionName) {
            if (sectionName == "appSettings") {
                this.appsettings = null;
            }

            if (sectionName == "connectionStrings") {
                this.connectionStrings = null;
            }

            clientConfigSystem.RefreshConfig(sectionName);
        }

        public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }

        #endregion
    }
}

To install this (or original version of configuration override) add following line to your Global. class (Global.asax.cs) in Application_Start

Williablog.Core.Configuration.ConfigSystem .Install();

like below:

public class Global: System.Web.HttpApplication {

    //...

    #region protected void Application_Start(...)
    protected void Application_Start(object sender, EventArgs e) {
        Williablog.Core.Configuration.ConfigSystem .Install();

        //...

    }
    #endregion

    //...

}
Alvin answered 28/3, 2013 at 3:21 Comment(0)
M
17

I know this is an old question, but I ran into the same problem and I found that Set works in the same way as Add, and does not throw an exception, so just replace Add with Set, like so:

ConfigurationManager.AppSettings.Set(item.Key, item.Value);
Mickel answered 7/6, 2017 at 18:4 Comment(0)
A
7

You need to make use of WebConfigurationManager.OpenWebConfiguration()

Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
config.AppSettings.Settings.Remove("Variable");
config.AppSettings.Settings.Add("Variable", "valyue");
config.Save();
Anyone answered 27/3, 2013 at 7:35 Comment(1)
Thanks for your answer, but I do not want to save values back to Web.Config file. on the other hand, with IIS settings this code will not work either as Web.Config is readonly unless you change Machine.config which is not allowed in shared serversAlvin
B
1

Perhaps this link will help. It references 2.0 but I believe the method is still valid in 4.0.

Also, the SO question on the same/similar topic here may be of interest.

Also, modifying the web.config at runtime should cause an application pool recycle each time. Not trying to tell you how to suck eggs, just thought I'd note it for anyone's prospective interest...Thx.

Belong answered 27/3, 2013 at 7:34 Comment(4)
Thank you for your answer, however the articles suggested here, also saving the values back to config file (which is not possible due to protection level of IIS unless you modify machine.config). you are right modifying the Web.config recycles the application pool which is not intended either.Alvin
AFAIK I don't think you can then. I think the only way would be to add another layer of indirection by creating a SecureConfigurationManager class with an AppSettings property which can be called just like ConfigurationManager's AppSettings (e.g. SecureConfigurationManager.AppSettings[]) but has logic in there to check your Dictionary first and fall back to ConfigurationManager.AppSettings[] if it doesn't find it (or vice versa). I note that this is a code change (not what you're after) but perhaps a low impact oneBelong
This would mean your developers wouldn't notice if the setting was coming from appSettings in the web.config or not and could still put things in there happily and your wrapper accounts for it at Production time when the settings are not there. However, I've probably gone off topic so please feed back to me if this isn't what you're after - and if it's not I'll delete this answer because it's not really adding anything to your question. Sorry I can't be of more help.Belong
Actually your answer was helpful. the first link you shared referred to another article on how to modify the ConfigurationManager class to read settings from other place. I'm looking into that article right now and I'll post my findings if it is successful here.Alvin
A
0

Thanks to nkvu which directed me to a his first link which in turn sent me to Williarob's post "Override Configuration Manager" I managed to find a solution to my question.

The mentioned blog post covers how to read settings from another XML file and it works with both windowed applications and web applications (with a little modification in config file name and path). Although this blog written on 2010 it is still working fine with .NET4 without problem.

However as I was going to read my configuration from a secure device, I simplified the class and here is how to use the classes provided by Williarob

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;

namespace Williablog.Core.Configuration {

    public sealed class ConfigSystem: IInternalConfigSystem {
        private static IInternalConfigSystem clientConfigSystem;

        private object appsettings;

        private object connectionStrings;

        /// <summary>
        /// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
        /// </summary>
        public static void Install() {
            FieldInfo[] fiStateValues = null;
            Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);

            if (null != tInitState) {
                fiStateValues = tInitState.GetFields();
            }

            FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
            FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);

            if (fiInit != null && fiSystem != null && null != fiStateValues) {
                fiInit.SetValue(null, fiStateValues[1].GetValue(null));
                fiSystem.SetValue(null, null);
            }

            ConfigSystem confSys = new ConfigSystem();
            Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
            IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
            configSettingsFactory.SetConfigurationSystem(confSys, false);

            Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
            clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
        }

        #region IInternalConfigSystem Members
        public object GetSection(string configKey) {
            // get the section from the default location (web.config or app.config)
            object section = clientConfigSystem.GetSection(configKey);

            switch (configKey) {
                case "appSettings":
                    // Return cached version if exists
                    if (this.appsettings != null) {
                        return this.appsettings;
                    }

                    // create a new collection because the underlying collection is read-only
                    var cfg = new NameValueCollection();

                    // If an AppSettings section exists in Web.config, read and add values from it
                    if (section is NameValueCollection) {
                        NameValueCollection localSettings = (NameValueCollection) section;
                        foreach (string key in localSettings) {
                            cfg.Add(key, localSettings[key]);
                        }
                    }

                    // --------------------------------------------------------------------
                    // Here I read and decrypt keys and add them to secureConfig dictionary
                    // To test assume the following line is a key stored in secure sotrage.
                    //secureConfig = SecureConfig.LoadConfig();
                    secureConfig.Add("ACriticalKey", "VeryCriticalValue");
                    // --------------------------------------------------------------------                        
                    foreach (KeyValuePair<string, string> item in secureConfig) {
                        if (cfg.AllKeys.Contains(item.Key)) {
                            cfg[item.Key] = item.Value;
                        } else {
                            cfg.Add(item.Key, item.Value);
                        }
                    }
                    // --------------------------------------------------------------------                        


                    // Cach the settings for future use
                    this.appsettings = cfg;
                    // return the merged version of the items from secure storage and appsettings
                    section = this.appsettings;
                    break;

                case "connectionStrings":
                    // Return cached version if exists
                    if (this.connectionStrings != null) {
                        return this.connectionStrings;
                    }

                    // create a new collection because the underlying collection is read-only
                    ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();

                    // copy the existing connection strings into the new collection
                    foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
                        connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
                    }

                    // --------------------------------------------------------------------
                    // Again Load connection strings from secure storage and merge like below
                    // connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
                    // --------------------------------------------------------------------                        

                    // Cach the settings for future use
                    this.connectionStrings = connectionStringsSection;
                    // return the merged version of the items from secure storage and appsettings
                    section = this.connectionStrings;
                    break;
            }

            return section;
        }

        public void RefreshConfig(string sectionName) {
            if (sectionName == "appSettings") {
                this.appsettings = null;
            }

            if (sectionName == "connectionStrings") {
                this.connectionStrings = null;
            }

            clientConfigSystem.RefreshConfig(sectionName);
        }

        public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }

        #endregion
    }
}

To install this (or original version of configuration override) add following line to your Global. class (Global.asax.cs) in Application_Start

Williablog.Core.Configuration.ConfigSystem .Install();

like below:

public class Global: System.Web.HttpApplication {

    //...

    #region protected void Application_Start(...)
    protected void Application_Start(object sender, EventArgs e) {
        Williablog.Core.Configuration.ConfigSystem .Install();

        //...

    }
    #endregion

    //...

}
Alvin answered 28/3, 2013 at 3:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.