SYSK 260: Custom Configuration Settings Just Like Ones Used in .NET Framework Classes

.NET 2.0 settings designer that generates a strongly typed class representing the applicationSetting and userSettings sections is a great improvement over the old appSettings or custom xml serialization code… But, sometimes you just need a bit more…

If you see the configuration below, what would you expect the output to be?

<add serverName="server1">

      <services>

            <add serviceName="Dhcp" started="true" />

            <add serviceName="MSSqlServer" started="true" />

<add serviceName="vds" started="true" />

      </services>

</add>

<add serverName="server2">

      <services copyFrom="server1">

            <override serviceName="MSSqlServer" started="false" />

            <remove serviceName="vds" />

      </services>

</add>

Of course, you’d expect something like this:

· On server1, services Dhcp, MSSqlServer and vds should be started

· On server2, Dhcp should be started, MSSqlServer should notbe started, and we don’t care about vds.

Now, the question is, how do you create a strongly typed configuration classes that would figure out all the add, remove and override keywords, and allow the client to use it in a very intuitive way… something like this:

ServiceMonitorSettingsSection settings = ServiceMonitorSettingsSection.Settings;

foreach (ServerSettings server in settings.Servers)

{

    foreach (ServiceSettings service in server.Services)

    {

        System.Diagnostics.Debug.WriteLine(string.Format("Service {0} is expected to be {1}started on server {2}",

           service.ServiceName, service.Started == true ? "" : "not ", server.ServerName));

    }

}

Below is the code that does just that!

Here is the full configuration file:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

      <configSections>

            <section name="ServiceMonitorSettings" type="WindowsApplication1.ServiceMonitorSettingsSection, WindowsApplication1" />

      </configSections>

      <ServiceMonitorSettings>

            <servers>

                  <add serverName="server1">

                        <services>

                              <add serviceName="abc" started="true" />

                              <add serviceName="abc2" started="true" />

                        </services>

                  </add>

                  <add serverName="server3">

                        <services copyFrom="server1">

                              <override serviceName="abc" started="false" />

                              <remove serviceName="abc2" />

                        </services>

                  </add>

            </servers>

                       

      </ServiceMonitorSettings>

</configuration>

And here is the actual code:

using System;

using System.Collections.Generic;

using System.Configuration;

using System.Xml;

namespace WindowsApplication1

{

    #region ServiceMonitorSettingsSection

    public sealed class ServiceMonitorSettingsSection : ConfigurationSection

    {

        private readonly ConfigurationProperty _servers =

            new ConfigurationProperty("servers", typeof(ServerCollection), null,

                        ConfigurationPropertyOptions.IsRequired);

        public static ServiceMonitorSettingsSection Settings

        {

            get

            {

                ServiceMonitorSettingsSection result = null;

                try

                {

                    result = (ServiceMonitorSettingsSection)System.Configuration.ConfigurationManager.GetSection("ServiceMonitorSettings");

                    if (result == null)

                        throw new ApplicationException("Configuration file is missing 'ServiceMonitorSettings' section", null);

                    else

                    {

                        // Implement copyFrom

                        foreach (ServerSettings serverSettings in result.Servers)

                        {

                            string copyFrom = serverSettings.Services.CopyFrom;

                            if (copyFrom != null && copyFrom.Trim().Length > 0)

                            {

                                ServerSettings from = result.Servers[copyFrom];

                                if (from != null)

                                {

                                    foreach (ServiceSettings fromService in from.Services)

                                    {

                                        serverSettings.Services.Add(fromService.Clone());

                                    }

                             // All done -- set to read only

                                    serverSettings.Services.SetInitialized();

                                }

                                else

                                {

                                  throw new ApplicationException(string.Format("Invalid copyFrom setting. Server {0} doesn't exist in configuration file", copyFrom), null);

                                }

                            }

                      }

                    }

                }

                catch (Exception ex)

                {

                    System.Diagnostics.Debugger.Break();

                    // TODO: log and rethrow

                }

            return result;

            }

        }

        public ServiceMonitorSettingsSection()

        {

        }

      

        [ConfigurationProperty("servers", Options = ConfigurationPropertyOptions.IsRequired)]

        public ServerCollection Servers

   {

            get

            {

                return (ServerCollection)base[_servers];

            }

        }

    }

    #endregion

    #region ServerCollection

    [ConfigurationCollection(typeof(ServerSettings))]

    public sealed class ServerCollection : ConfigurationElementCollection

    {

        public ServerCollection()

            : base(StringComparer.OrdinalIgnoreCase)

        {

        }

        public new ServerSettings this[string serverName]

        {

            get

            {

   // Force the get by key, not index

                object key = serverName;

                return (ServerSettings)base.BaseGet(key);

            }

        }

        public ServerSettings this[int index]

        {

            get

            {

                return (ServerSettings)base.BaseGet(index);

            }

        }

      

        protected override ConfigurationElement CreateNewElement()

        {

            return new ServerSettings();

        }

        protected override Object GetElementKey(ConfigurationElement element)

        {

            return ((ServerSettings)element).ServerName;

        }

    }

    #endregion

    #region ServerSettings

    public sealed class ServerSettings : ConfigurationElement

    {

        internal static readonly ConfigurationValidatorBase NonEmptyStringValidator = new StringValidator(1);

        private readonly ConfigurationProperty _serverName =

            new ConfigurationProperty("serverName", typeof(string), String.Empty, null, null,

       ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey);

        private readonly ConfigurationProperty _services =

            new ConfigurationProperty("services", typeof(ServiceCollection), new ServiceCollection(), null, null,

                                        ConfigurationPropertyOptions.None);

       

        public ServerSettings()

        {

        }

        [ConfigurationProperty("serverName", Options = ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey, DefaultValue = "")]

        public string ServerName

        {

            get { return (string)base[_serverName]; }

            set { base[_serverName] = value; }

        }

        [ConfigurationProperty("services", Options = ConfigurationPropertyOptions.None)]

        public ServiceCollection Services

        {

            get { return (ServiceCollection)base[_services]; }

            set { base[_services] = value; }

        }

    }

    #endregion

   

    #region ServiceCollection

    [ConfigurationCollection(typeof(ServiceSettings))]

    public sealed class ServiceCollection : ConfigurationElementCollection

    {

        private string _copyFrom = null;

        private Dictionary<string, ServiceSettings> _overrides = new Dictionary<string, ServiceSettings>();

        private Dictionary<string, ServiceSettings> _removes = new Dictionary<string, ServiceSettings>();

        public ServiceCollection()

            : base(StringComparer.OrdinalIgnoreCase)

        {

           

        }

        // Called by Copy

        internal void Add(ServiceSettings element)

        {

            this.BaseAdd(element);

        }

        protected override void SetReadOnly()

        {

            // Ignore, so we can set it after overrides are done...

        }

        internal void SetInitialized()

        {

            // Implement override

            foreach (string serviceName in _overrides.Keys)

            {

      if (this[serviceName] != null)

                {

                    this[serviceName].CopyFrom(_overrides[serviceName]);

                }

            }

            // Removes marked items

            foreach (string serviceName in _removes.Keys)

            {

                base.BaseRemove(serviceName);

            }

            base.SetReadOnly();

            _overrides = null;

            _removes = null;

        }

        protected override bool OnDeserializeUnrecognizedAttribute(string name, string value)

        {

            if (string.Compare(name, "copyFrom", true) == 0)

            {

                _copyFrom = value;

                // Handled, for now... The actual copying will be done in ServiceMonitorSettingsSection.Settings

                return true;

            }

            else

               return base.OnDeserializeUnrecognizedAttribute(name, value);

        }

        public string CopyFrom

        {

            get { return _copyFrom; }

            set { _copyFrom = value; }

        }

          

        public new ServiceSettings this[string serviceName]

        {

            get

            {

                // Force the get by key, not index

                object key = serviceName;

                return (ServiceSettings)base.BaseGet(key);

            }

        }

        public ServiceSettings this[int index]

        {

            get

            {

                return (ServiceSettings)base.BaseGet(index);

            }

        }

        protected override ConfigurationElement CreateNewElement()

        {

            return new ServiceSettings();

        }

        protected override Object GetElementKey(ConfigurationElement element)

        {

            return ((ServiceSettings)element).ServiceName;

        }

          

        protected override bool OnDeserializeUnrecognizedElement(String elementName, XmlReader reader)

        {

            bool handled = false;

            if (elementName == "override")

            {

      ServiceSettings elem = new ServiceSettings(reader);

                _overrides.Add(elem.ServiceName, elem);

               

                handled = true;

            }

            else if (elementName == "remove")

            {

               ServiceSettings elem = new ServiceSettings(reader);

                _removes.Add(elem.ServiceName, elem);

                handled = true;

            }

            else

                return base.OnDeserializeUnrecognizedElement(elementName, reader);

 

            return handled;

        }

    }

    #endregion

    #region ServiceSettings

    public sealed class ServiceSettings : ConfigurationElement

    {

        private readonly ConfigurationProperty _serviceName =

            new ConfigurationProperty("serviceName", typeof(string), String.Empty, null, null,

                                        ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey);

        private readonly ConfigurationProperty _started =

      new ConfigurationProperty("started", typeof(bool), true, ConfigurationPropertyOptions.None);

        public ServiceSettings()

        {

        }

        public ServiceSettings(XmlReader reader)

        {

            base.DeserializeElement(reader, false);

        }

        [ConfigurationProperty("serviceName", Options = ConfigurationPropertyOptions.IsRequired | ConfigurationPropertyOptions.IsKey, DefaultValue = "")]

        public string ServiceName

        {

            get { return (string)base[_serviceName]; }

            set { base[_serviceName] = value; }

        }

        [ConfigurationProperty("started", Options = ConfigurationPropertyOptions.None, DefaultValue = true)]

        public bool Started

        {

            get { return (bool)base[_started]; }

            set { base[_started] = value; }

        }

        public void CopyFrom(ServiceSettings item)

        {

            foreach (ConfigurationProperty prop in item.Properties)

            {

                this[prop.Name] = item[prop];

            }

        }

        public ServiceSettings Clone()

        {

            return this.MemberwiseClone() as ServiceSettings;

        }

    }

    #endregion

}