Cyber Security & Dot Net Security

Thursday, November 18, 2010

Modify app.config On-The-Fly

In this installation of my blog series I’ll cover the first concrete item listed in the introduction. Implement a WinForm that modifies its own Config file.

Let’s start by creating a Main form for the UI that will be our work area for the Code Generator.  We add a button “Modify Config” that executes the following code to launch the frmAppConfig form:
private void btnModifyConfig_Click(object sender, EventArgs e)        
{
            frmAppConfig dlgAppconfig = new frmAppConfig();
            dlgAppconfig.ShowDialog();        
}
The form itself looks like this at this point:
 The frmAppConfig executes the following LoadConfigValues code called from its constructor:
        private void LoadConfigValues()        
        {
            try
            {
                NameValueCollection appSettings
                    = ConfigurationManager.AppSettings;
                dgvAppConfigSettings.Rows.Clear();
                dgvAppConfigSettings.Rows.Add(appSettings.Count);
                int i = 0;
                foreach (string key in appSettings)
                {
                    string value = appSettings[key];
                    DataGridViewRow row = dgvAppConfigSettings.Rows[i++];
                    row.Cells["Key"].Value = key;
                    row.Cells["Value"].Value = value;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message,
                                "Setting Application Configuration Error",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Stop);
            }
        } 
As you can see the method uses the System.Configuration.ConfigurationManager.AppSettings property to get a System.Collections.Specialized.NameValueCollection of the app settings in the app.config file.  If we open the app.config file we’ll see the following:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <clear />
    <add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
    <add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
    <add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
    <add key="DeveloperName" value="John P. Frampton" />
    <add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
    <add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
    <add key="TableDefinitionFilePath" value="C:\Syrinx\Syrinx.DB\Tables\" />
  </appSettings>
  <connectionStrings>
    <add
      name="FakeConnectionString"
      connectionString="NotReally"/>
  </connectionStrings>
</configuration>
Note that there is a connectionStrings section in the app.config with which we are not concerned and which will not be modified.  If we run our application, not in debug mode, but with the actual compiled .exe, the frmAppConfig form will display the following:
Starting App.config 
As expected the DataGridView is populated with values from the appSettings section of the Config file.  The only thing we have left to do is persist any changes back into the app.config file.  That is accomplished with the following code:
    private void PersistAppSettings()        
    {
            Hashtable htAppConfigValuesInGrid = new Hashtable();
            // Load the existing Grid values into a hashtable
            htAppConfigValuesInGrid.Clear();
            foreach (DataGridViewRow row in dgvAppConfigSettings.Rows)
            {
                if (row.Cells["Key"].Value != null)
                {
                    htAppConfigValuesInGrid.Add(row.Cells["Key"].Value, row.Cells["Value"].Value);
                }
            }
            // Open App.Config of executable and get AppSettingsSection
            Configuration config
                = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");
            appSettings.Settings.Clear();
            // Go through the hashtable and see if the value exists in the config file
            // if so update it
            // if not, add it
            foreach (DictionaryEntry entry in htAppConfigValuesInGrid)
            {
                string key = entry.Key.ToString();
                KeyValueConfigurationElement kvcElement = appSettings.Settings[key];
                // exists in the grid but not in the appSettings, add the missing value
                if (kvcElement == null)
                {
                    appSettings.Settings.Add(key, (string)htAppConfigValuesInGrid[key]);
                }
                else
                {
                    // only update the value if it has changed
                    if (kvcElement.Value != (string)htAppConfigValuesInGrid[key])
                    {
                        appSettings.Settings[key].Value = (string)htAppConfigValuesInGrid[key];
                    }
                }
            }
             // Go through the settings and remove any that are not in the grid hashtable
            foreach (KeyValueConfigurationElement element in config.AppSettings.Settings)
            {
                string keyVal = (string)htAppConfigValuesInGrid[element.Key];
                if (keyVal == null)
                {
                    appSettings.Settings.Remove(element.Key);
                }
            }
            // Save the configuration file.
            //config.Save(ConfigurationSaveMode.Modified, true);
            config.Save();
            // Force a reload of a changed section.
            ConfigurationManager.RefreshSection("appSettings");
        }
    }
If we add a new setting and modify an existing one, the UI will show this:
Modified app.config 
And the executables app.config (SyrinxCodeGenerator.exe.config) will reflect the addition and update like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <clear />
    <add key="DeveloperName" value="John P. Frampton" />
    <add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
    <add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
    <add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
    <add key="TableDefinitionFilePath _I_HAVE_CHANGED" value="C:\Syrinx\Syrinx.DB\Tables\" />
    <add key="NEW_APP_SETTING" value="THIS_IS_NEW" />
    <add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
    <add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
  </appSettings>
  <connectionStrings>
    <add
      name="FakeConnectionString"
      connectionString="NotReally"/>
  </connectionStrings>
</configuration>
If we remove the new field from the UI we see that the app.config file reflects it like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <clear />
    <add key="TableDefinitionFilePath _I_HAVE_CHANGED" value="C:\Syrinx\Syrinx.DB\Tables\" />
    <add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
    <add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
    <add key="DeveloperName" value="John P. Frampton" />
    <add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
    <add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
    <add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
  </appSettings>
  <connectionStrings>
    <add
      name="FakeConnectionString"
      connectionString="NotReally"/>
  </connectionStrings>
</configuration>
That wraps it up for this installation, next we’ll look at saving and loading definition files to and from XML.

No comments: