JB's BLOG

How to create a custom type for app.config and web.config in .NET

We are creating a configuration for a crazy cat lady.

  1. Reference System.Configuration in your project

    This contains many of the classes and attributes we will be using.

  2. Create a class to represent individual cats, one to represent a collection of cats, and finally one for the lady.

    • The ConfigurationElement class

      Represents a configuration element within a configuration file. We can define attributes and children by using the ConfigurationProperty attribute on member properties.

      Here we define a cat type with three attribute properties: name, color, and age.

      We demonstrate some of the features of ConfigurationPropertyAttribute, marking the latter two as optional, and giving age a default.

      using System.Configuration;
      namespace CatLadyDemo
      {
          public class CatConfiguration : ConfigurationElement
          {
              private const string NameKey = "name";
              private const string ColorKey = "color";
              private const string AgeKey = "age";
              private const string NoAgeSpecified = -1;
      
              [ConfigurationProperty(NameKey, IsRequired = true)]
              public string XmlNamespace => (string) this[NameKey];
      
              [ConfigurationProperty(ColorKey, IsRequired = false)]
              public string Name => (string) this[ColorKey];
      
              [ConfigurationProperty(AgeKey, IsRequired = false, DefaultValue = NoAgeSpecified)]
              public int Age => (int) this[AgeKey];
          }
      }
    • The ConfigurationElementCollection class

      Represents a configuration element containing a collection of child elements. One important thing to note is that it's children must be uniquely identifiable. The GetElementKey function is responsible for determining how to derive a key from a child element.

      This will be the class representing the many cats of the crazy cat lady.

      The ConfigurationCollectionAttribute's AddItemName parameter defines the name of each entry in the collection.

      Had we left it blank, the cats element's children would be add, this way they are cat elements instead.

      We also declare that each cat's name is a unique key identifying them.

      using System.Configuration;
      namespace CatLadyDemo
      {
          [ConfigurationCollection(typeof(CatConfiguration), AddItemName = CatKey)]
          public class CatConfigurationCollection : ConfigurationElementCollection
          {
              private const string CatKey = "cat";
      
              protected override ConfigurationElement CreateNewElement()
              {
                  return new CatConfiguration();
              }
      
              protected override object GetElementKey(ConfigurationElement element)
              {
                  return ((CatConfiguration) element).Name;
              }
          }
      }
    • The ConfigurationSection class

      Represents a section within a configuration file.

      The topmost level of our configurations, this class represents the crazy cat lady.

      Whereas CatConfiguration used ConfigurationPropertyAttribute to define attributes, here we use it for attributes and a child element.

      Because we are going to define an xml schema, we add an attribute named xmlns. This is necessary because in the App.config we use xmlns to specify which schema catLady comes from. Omitting a corresponding backing variable breaks deserialization, so we include it.

      
      using System.Configuration;
      namespace CatLadyDemo
      {
          public class CatLadyConfigurationSection : ConfigurationSection
          {
              private const string XmlnsKey = "xmlns";
              private const string NameKey = "name";
              private const string CatsKey = "cats";
      
              [ConfigurationProperty(XmlnsKey, IsRequired = false)]
              public string XmlNamespace => (string) this[XmlnsKey];
      
              [ConfigurationProperty(NameKey, IsRequired = true)]
              public string Name => (string) this[NameKey];
      
              [ConfigurationProperty(CatsKey, IsRequired = true)]
              public CatConfigurationCollection Cats => (CatConfigurationCollection) this[CatsKey];
          }
      }
  3. Create an xml schema for your configuration. (Optional)

    This gets rid of the Visual Studio message Could not find schema information for the element 'catLady'. It also provides auto-completion tips and will notify users if anything is missing.

    <?xml version="1.0" encoding="utf-8" ?>
    <xs:schema
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns="https://jbp.dev/CatLadySchema"
        targetNamespace="https://jbp.dev/CatLadySchema"
        elementFormDefault="qualified">
    
        <!-- The elements that can appear in a document -->
        <xs:element name="catLady" type="catLadyType"/>
    
        <!-- Type definitions -->
        <xs:complexType name="catLadyType">
            <xs:sequence>
                <xs:element name="cats" type="catListType" minOccurs="1" maxOccurs="1"/>
            <xs:sequence>
            </xs:attribute name="name" type="xs:string" use="required"/>
        </xs:complexType>
    
        <xs:complexType name="catListType">
            <!-- If she has fewer than 3 cats is she really a crazy cat lady? -->
            <xs:sequence>
                <xs:element name="cat" type="catType" minOccurs="4" maxOccurs="unbounded"/>
            </xs:sequence>
        </xs:complexType>
    
        <xs:complexType name="catType">
            <xs:attribute name="name" type="xs:string" use="required"/>
            <xs:attribute name="color" type="xs:string" use="optional"/>
            <xs:attribute name="age" type="xs:int" use="optional"/> 
        </xs:complexType>
    </xs:schema>
    
  4. Point the App.config to your new schema (Optional)

    With the App.config open, go to the properties tab and put your mouse in the box for the Schemas property.

    Click the ... button that appears on the right.

    This pops up the Schemas window, add and enable your new xsd file to the list. It might already be in there but not checked.

  5. Declare your ConfigurationSection in the App.config

    In your App.config, add your element as a child of the configSections element:

    <section name="catLady" type="CatLadyDemo.CatLadyConfigurationSection, CatLadyDemo"/>

    If you signed your assembly, you'll need more information in your type definition, including the PublicKeyToken. You can learn how to obtain it on msdn.

    <section name="catLady" type="CatLadyDemo.CatLadyConfigurationSection, CatLadyDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=487a851583594efc"/>
  6. Add your ConfigurationSection to the App.config

    Add your section as a child of the configuration element:

    <catLady
        name="Chelsea"
        xmlns="https://jbp.dev/CatLadySchema">
        <cats>
            <cat name="Smokey"/>
            <cat name="Garfield" color="Tabby"/>
            <cat name="Furby" age="3"/>
            <cat name="Vanilla" age="5" color="White"/>
        </cats>
    </catLady>
  7. Accessing your configuration section in code.

    using System;
    using System.Configuration;
    
    namespace CatLadyDemo
    {
        public class Program
        {
            private static CatLadyConfigurationSection GetConfig()
            {
                return (CatLadyConfigurationSection) ConfigurationManager.GetSection("catLady")
            }
    
            public static void Main(string[] args)
            {
                var x = GetConfig();
                Console.WriteLine(x.Name);
                foreach(var catElement in x.Cats)
                {
                    var cat = (CatConfiguration) catElement;
                    var color = string.IsNullOrEmpty(cat.Color) ? "" : " color:" + cat.Color;
                    var age = cat.Age == CatConfiguration.NoAgeSpecified ? "" : " age:" + cat.Age;
                    Console.WriteLine(cat.Name + color + age);
                }
    
                Console.ReadLine();
            }
        }
    }
  8. More ideas

    You can add schema restrictions and .NET validators such as IntegerValidator.

    Above Age in CatConfiguration.cs add:

    [IntegerValidator(MinValue = NoAgeSpecified)]

    In CatLadySchema.xsd, change catType.age.type to "ageType" and to the bottom add:

    <xs:simpleType name="ageType">
        <xs:restriction base="xs:int">
            <xs:minInclusive value="-1"/>
        </xs:restriction> 
    </xs:simpleType>