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 not be 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


 


}


 


 


 

Comments (0)