Monday, December 5, 2011

Create custom config section handler

In order to create a custom config section handler we would use the following namespaces:

System.Configuration
System.Collections
System.Collections.Generic
System.Collections.Specialized

Now a sample xml for which we intend to create a config section handler:

<TransformerConfigurationSection MaxSlotsPerMachine="5">
    <!-- Remote Service Machines-->
    <servicemachine id="01">
      <Id value="1" />
      <Name value="server1" />     
    </servicemachine>
    <servicemachine id="02">
      <Id value="2" />
      <Name value="server2" />
    </servicemachine>
</TransformerConfigurationSection>

Now first we create a ConfigurationSection Class that reads this section from a config file. The code looks as follows:


public class TransformerConfigurationSection : ConfigurationSection 
{
    // 1. MaxSlotsPerMachine -- attribute
    // 2. serviceMachine -- Element collection
 
    public const string SectionXPath = "TransformerConfigurationSection";
    public TransformerConfigurationSection()
    {
        Properties.Add(new ConfigurationProperty("MaxSlotsPerMachine", typeof(string), null));
    }
 
    public static TransformerConfigurationSection GetSection()
    {
        return (TransformerConfigurationSection)ConfigurationManager.GetSection(SectionXPath);
    }
 
    [ConfigurationProperty("MaxSlotsPerMachine")]
    public string MaxSlotsPerMachine
    {
        get
        {
            return (string)this["MaxSlotsPerMachine"];
        }
    }
 
    [ConfigurationProperty("", Options = ConfigurationPropertyOptions.IsDefaultCollection)]
    public MachineElementCollection ServiceMachines
    {
        get
        {
            return (MachineElementCollection)this[""];
        }
    }
}

Now we create the class MachineElementCollection that represents a ConfigurationElementCollection.

public class MachineElementCollection : MachineConfigurationElementCollection<ServiceMachineElement>
{
    public MachineElementCollection()
        :base("servicemachine")
    {  
    }
 
    protected override object  GetElementKey(ConfigurationElement element)
    {
        return ((ServiceMachineElement) element).Id;
    }
}
Now create a base class that allows you to reuse the ConfigurationElementCollection to be of any type.

public abstract class MachineConfigurationElementCollection<T> : ConfigurationElementCollection where T: ConfigurationElement, new()
{
    private ConfigurationElementCollectionType collectionType;
    private string elementName;
    public override ConfigurationElementCollectionType  CollectionType
    {
        get 
        { 
                return collectionType;
        }
    }
 
    protected override string  ElementName
    {
        get
        {
            return String.IsNullOrEmpty(elementName) ? base.ElementName : elementName;
        }
    }
 
    public virtual T this[object key]
    {
        get
        {
            var configurationElementType = (T)BaseGet(key);
            return (object)configurationElementType != null ? configurationElementType : null;
        }
        set
        {
            if (IsReadOnly())
                Add(value);
            if (GetElementKey(value).ToString().Equals((string)key, StringComparison.Ordinal))
            {
                if (BaseGet(key) != null)
                    BaseRemove(key);
                Add(value);
            }
        }
    }
 
    public T this[int index]
    {
        get
        {
            return (T)BaseGet(index);
        }
        set
        {
            if (!IsReadOnly() && !ThrowOnDuplicate && BaseGet(index) != null)
                BaseRemoveAt(index);
            BaseAdd(index, value);
        }
    }
 
    internal MachineConfigurationElementCollection()
        : this(ConfigurationElementCollectionType.AddRemoveClearMap, null)
    {
    }
 
    internal MachineConfigurationElementCollection(string elementname)
        : this(ConfigurationElementCollectionType.AddRemoveClearMap, elementname)
    {
    }
 
    internal MachineConfigurationElementCollection(ConfigurationElementCollectionType collectionType, string elementName)
    {
        this.collectionType = collectionType;
        this.elementName = elementName;
        if (string.IsNullOrEmpty(elementName))
            return;
        AddElementName = elementName;
    }
 
    internal MachineConfigurationElementCollection(ConfigurationElementCollectionType collectionType, string elementName, IComparer comparer)
        : base(comparer)
    {
        this.collectionType = collectionType;
        this.elementName = elementName;
    }
 
    protected override void BaseAdd(ConfigurationElement element)
    {
      if (!IsReadOnly() && !ThrowOnDuplicate)
      {
        object elementKey = this.GetElementKey(element);
        if (ContainsKey(elementKey))
          BaseRemove(elementKey);
      }
 
      base.BaseAdd(element);
    }
 
    public void Add(T element)
    { 
      BaseAdd(element);
    }
 
    public void Clear()
    {
      BaseClear();
    }
 
    public virtual bool ContainsKey(object key)
    {
      if (key != null)
        return null != BaseGet(key);
      var list = new List<string>();
 
      foreach (PropertyInformation propertyInformation in (NameObjectCollectionBase) CreateNewElement().ElementInformation.Properties)
      {
        if (propertyInformation.IsKey)
          list.Add(propertyInformation.Name);
      }
        return false;
    }
 
    protected override ConfigurationElement CreateNewElement()
    {
      return Activator.CreateInstance<T>();
    }
}
Now since the ConfigurationElementCollection has been created now we create the actual ConfigurationElement.

public class ServiceMachineElement : ConfigurationElement
{
    public ServiceMachineElement()
    {
        Properties.Add(new ConfigurationProperty(ServiceMachineIdKey, typeof(string), ""));
        Properties.Add(new ConfigurationProperty(IdKey, typeof(IdElement), null));
        Properties.Add(new ConfigurationProperty(NameKey, typeof(NameElement), null));
    }
 
    private const string ServiceMachineIdKey = "id";
    [ConfigurationProperty(ServiceMachineIdKey, IsRequired = false)]
    public string ServiceMachineId
    {
        get { return (String)this[ServiceMachineIdKey]; }
        set { this[ServiceMachineIdKey] = value; }
    }    
 
    private const string IdKey = "Id";
    [ConfigurationProperty(IdKey, IsRequired = true)]
    public IdElement Id
    {
        get { return (IdElement)this[IdKey]; }
        set { this[IdKey] = value; }
    }
 
    private const string NameKey = "Name";
    [ConfigurationProperty(NameKey, IsRequired = true)]
    public NameElement Name
    {
        get { return (NameElement)this[NameKey]; }
        set { this[NameKey] = value; }
    }    
}
Since, we have the servicemachine element with child elements of type NameElement and IdElement we create these classes.

public class IdElement : ConfigurationElement
{
    public IdElement()
    {
        Properties.Add(new ConfigurationProperty(ValueKey, typeof(string), null));
    }
 
    private const string ValueKey = "value";
    [ConfigurationProperty(ValueKey, IsRequired = true)]
    public string Value
    {
        get { return (string)this[ValueKey]; }
        set { this[ValueKey] = value; }
    }
}

public class NameElement : ConfigurationElement
    {
        public NameElement()
        {
            Properties.Add(new ConfigurationProperty(ValueKey, typeof(string), null));
        }
 
        private const string ValueKey = "value";
 
        [ConfigurationProperty(ValueKey, IsRequired = true)]
        public string Value
        {
            get { return (string)this[ValueKey]; }
            set { this[ValueKey] = value; }
        }
    }

That is it. The config section reader is done. Now just add the above xml to your app.config or web.config and define the configsection as shown below:

<configuration>
  <configSections>
    <section name="TransformerConfigurationSection" type="Sample.TransformerConfigurationSection, Sample"/>
  </configSections>
</configuration>

No comments: