Geeklog
There's no place like 127.0.0.1 - ramblings about programming

Advanced Object Builder - Custom Configuration Sections

April 16, 2008 21:18 by The Geek

This post continues from a previous post which you can read here.

Today, I am going to write about how you can use custom configuration sections.  Since .Net 2.0 this has been a really easy thing to do and far more elegant than using application settings or custom files.  The object builder will require settings for two of its features - support for factory methods and type substitution.

*.config File

Before I start showing you the code, I think it is a good idea to show you what we are trying to acheive.  The following is a snippet from an imaginary *.config file.

<configSections>
  <section name="typeSubstitutionConfiguration" type="ObjectBuilder_Config.ObjectBuilder.Configuration.TypeSubstitutionConfiguration, ObjectBuilder_Config" />
  <section name="factoryMethodConfiguration" type="ObjectBuilder_Config.ObjectBuilder.Configuration.FactoryMethodConfiguration, ObjectBuilder_Config" />
</configSections> 

<configuration>

  <typeSubstitutionConfiguration>
    <conversions>

      <add originalType="ICat"
           replacementType="Cat"
           replacementAssembly="ObjectBuilder_Config" />

      <add originalType="IDog"
           replacementType="Dog"
           replacementAssembly="ObjectBuilder_Config" />
     
    </conversions>
  </typeSubstitutionConfiguration>

  <factoryMethodConfiguration>
    <methods>

      <add typeToBuild="Cat"
           factoryType="Cat"
           factoryAssembly="ObjectBuilder_Config"
           factoryMethod="Create" />

      <add typeToBuild="Dog"
           factoryType="Dog"
           factoryAssembly="ObjectBuilder_Config"
           factoryMethod="Create" />
     
    </methods>
  </factoryMethodConfiguration>

</configuration>

configSections

This allows us to specify the name of a custom section and the type that should be used to parse it.

typeSubstitutionConfiguration

This section can specify zero or more items which will allow the object builder to perform type substitution.  What will happen is that when the object builder is asked to build an instance of type ICat, it will actually build and return an instance of Cat (found in ObjectBuilder_Config).  Of course, Cat must implement ICat.

factoryMethodConfiguration

As with the type substitution configuration, the factory method configuration section can specify one or more factory methods.  When the object builder attempts to create an instance of Cat, it will return the result of the Create method of Cat, again, found in ObjectBuilder_Config.  The result of the Create method must be an instance of Cat although the method could be located in another class and in another assembly.

Implementation

I am only going to discuss the type substitution section as it is indentical to the factory methods section, with the exception of different properties.  If you want more info, contact me, leave a comment or download the source code.

Item Class

The first class that is required is a representation of an individual item:

    /// <summary>
    /// Represents a type conversion configuration element.
    /// </summary>
    public class TypeSubstitutionItem
        : ConfigurationElement
    {
        /// <summary>
        /// The attribute name to be used for the original type
        /// </summary>
        private const string OriginalTypeKey = "originalType";

        /// <summary>
        /// The attribute name to be used for the replacement type
        /// </summary>
        private const string ReplacementTypeKey = "replacementType";

        /// <summary>
        /// The attribute name to be used for the replacement type's assembly
        /// </summary>
        private const string ReplacementAssemblyKey = "replacementAssembly";

        /// <summary>
        /// Default constructor.
        /// </summary>
        public TypeSubstitutionItem() { }

        /// <summary>
        /// A <see cref="string"/> representing the type that is to be replaced.
        /// </summary>
        [ConfigurationProperty(OriginalTypeKey, IsRequired = true, IsKey = true)]
        public string OriginalType
        {
            get { return (string)this[OriginalTypeKey]; }
            set { this[OriginalTypeKey] = value; }
        }

        /// <summary>
        /// A <see cref="string"/> representing the type to build and return.
        /// </summary>
        [ConfigurationProperty(ReplacementTypeKey, IsRequired = true)]
        public string ReplacementType
        {
            get { return (string)this[ReplacementTypeKey]; }
            set { this[ReplacementTypeKey] = value; }
        }

        /// <summary>
        /// A <see cref="string"/> representing the assembly that contains the replacement type.
        /// </summary>
        [ConfigurationProperty(ReplacementAssemblyKey, IsRequired = true)]
        public string ReplacementAssembly
        {
            get { return (string)this[ReplacementAssemblyKey]; }
            set { this[ReplacementAssemblyKey] = value; }
        }
    }

The class inherits System.Configuration.ConfigurationElement so that we don't have to worry about deserialising the values that are stored in the *.config file.  As you can see, we handle properties in a different manner to the usual method of accessing member variables.  A few points about the properties:

  1. I use constants to ensure that errors are rare and changes are easy.
  2. The keys used to identify the properties must match the names of the attributes in the *.config.
  3. All properties must be decorated with a ConfigurationPropertyAttribute.
  4. Properties should access values via the base class's default property (using the key).
Collection Class

I have created a ConfigurationElementCollection<T> class which inherits System.Configuration.ConfigurationElementCollection - you can find it in the attached source code.  This class makes it very easy to create a strongly typed configuration collection:

    /// <summary>
    /// A collection of type conversion configuration elements.
    /// </summary>
    public sealed class TypeSubstitutionCollection
        : ConfigurationElementCollection<TypeSubstitutionItem>
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        public TypeSubstitutionCollection() { }

        /// <summary>
        /// Get the OriginalType value of the supplied <see cref="TypeSubstitutionItem"/>.
        /// </summary>
        /// <param name="element">
        /// The <see cref="TypeSubstitutionItem"/> whose OriginalType is to be returned.
        /// </param>
        /// <returns>The OriginalType value of the supplied <see cref="TypeSubstitutionItem"/>.</returns>
        protected override object GetElementKey(TypeSubstitutionItem element)
        {
            return element.OriginalType;
        }
    }

Configuration Class

The final piece of the puzzle is a class that represents the custom configuration section:

    /// <summary>
    /// Represents a type conversion configuration section within the application's *.config.
    /// </summary>
    public class TypeSubstitutionConfiguration
        : ConfigurationSection
    {
        /// <summary>
        /// The name of the section.
        /// </summary>
        public const string TypeSubstitutionConfigurationName = "typeSubstitutionConfiguration";

        /// <summary>
        /// The name of the collection within the section.
        /// </summary>
        private const string ConversionsName = "conversions";

        /// <summary>
        /// Constructor.
        /// </summary>
        public TypeSubstitutionConfiguration() { }

        /// <summary>
        /// A collection of type conversion configuration elements.
        /// </summary>
        [ConfigurationProperty(ConversionsName, IsRequired = true)]
        public TypeSubstitutionCollection Conversions
        {
            get { return (TypeSubstitutionCollection)base[ConversionsName]; }
        }
    }

Summary

This post has turned out to be quite long.  Sorry about that.  Hopefully you now understand just how easy it is to add a custom configuration section - my next post will show how you can access the settings and use factory methods.

Source code: ObjectBuilder_Config.zip (68.54 kb)


Comments