Thursday, April 8, 2010

Updatting c# applicationSettings in a ASP.NET Web Application

I have a few .NET web applications, using MVC, that make use of applicationSettings in their configuration.  These settings are semi-constant but do need to be updated from time to time.  I was trying to make an edit screen in the web application for developers so they could edit the applicationSettings without having to get on the server and manually edit the Web.config file.  As expected the applicationSettings are read only when accesses directly in the application and can not be updated.  Also it's not possible to configure the settings as userSettings when running as a web application.   Though we could do this by manualy reading and writing the file I was looking for a simpler way to do it.

After some tinkering I found a fairly simple way of doing this.  Basically we can use a custom ConfigurationManager instance to read the Web.config independently of the application and update this instance of the configuration.  Then we just call the save method and the edited data is saved out.  Here is the code for a simple update call.

Note that this code is tailored for MVC and is looping tough the FormCollection values. You could also explicitly read the post variables as parameters of the post action, or use this outside of MVC entirely. Just keep in mind that however you do it you can't loop by clientSection.Settings as the requirement to remove/re-add each updated value prevents this.


using System.Configuration;

[AcceptVerbs(HttpVerbs.Post), Authorize(Roles = "Admin")]
public ActionResult SaveSettings(FormCollection collection)
{
/* This section of code uses a custom configuration manager to edit the Web.config application settings.
 * These settings are normally read only but web apps don't support user scoped settings.
 * This set of variables is used for system features, not runtime tracking so it it only updated when an 
 *  administrator logs in to reconfigure the system.
 * 
 * Author: Jeremy Pyne 
 * Licence: CC:BY/NC/SA  http://creativecommons.org/licenses/by-nc-sa/3.0/
 */

// Load the Web.config file for editing.  A custom mapping to the file is needed as the default to to match the application's exe filename witch we don't have.
System.Configuration.Configuration config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap() {ExeConfigFilename = HttpContext.Server.MapPath("..\\Web.config") }, ConfigurationUserLevel.None);

// Find the applicationSettings group.
ConfigurationSectionGroup group = config.SectionGroups["applicationSettings"];
if (group == null)
    throw new AjaxException("Could not find application settings.");

// Find this applications section. Note: APP needs to be replaced with the namespace of your project.
ClientSettingsSection clientSection = group.Sections["APP.Properties.Settings"] as ClientSettingsSection;
if (clientSection == null)
        throw new AjaxException("Could not find Hines settings.");

// Loop through each value we are trying to update.
foreach (string key in collection.AllKeys)
{
    // Look for a setting in the config that has the same name as the current variable.
    SettingElement settingElement = clientSection.Settings.Get(key);

    // Only update values that are present in the config file.
    if (settingElement != null)
    {
        string value = collection[key];

        // Is this is an xml value then we need to do some conversion instead.  This currently only supports the StringCollection class.
        if (settingElement.SerializeAs == SettingsSerializeAs.Xml)
        {
            // Convert the form post (bob,apple,sam) to a StringCollection object.
            System.Collections.Specialized.StringCollection sc = new System.Collections.Specialized.StringCollection();
            sc.AddRange(value.Split(new char[] { ',' }));

            // Make an XML Serilization of the new StringCollection
            System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(System.Collections.Specialized.StringCollection));
            System.IO.StringWriter writer = new System.IO.StringWriter();
            ser.Serialize(writer, sc);

            // Get the xml code and trim the xml definition line from the top.
            value = writer.ToString().Replace("<?xml version=\"1.0\" encoding=\"utf-16\"?>", "");
        }

        // This is a custom override for MVC checkboxes.  They post as 'false' when unchecked and 'true,false' when selected.
        if(value == "true,false")
            value = "True";
        if(value == "false")
            value = "False";

        // Replace the settings with a updated settings.  It is necessary to do it this way instead of 
        //  updating it in place so that the configuration manager recognize that the setting has changed.
        // Also we can't just look through clientSection.Settings and update that way because then we wouldn't
        //  to do this exact thing.
        clientSection.Settings.Remove(settingElement);
        settingElement.Value.ValueXml.InnerXml = value;
        clientSection.Settings.Add(settingElement);
    }
}

// Save any changes to the configuration file.  Don't set forceSaveAll or other parts of the Web.config will get overwritten and break.
config.Save(ConfigurationSaveMode.Full);
}

No comments: