.netconfigurationsystem.configuration

How to Create a Configuration Section That Contains a Collection of Collections?


I need a configuration section something like this:

<myConfig>
    <mySubConfig1>
        <mySubSubConfig1 keyAttribute="value1">
            <mySubSubConfig1Element keyAttribute="value1"/>
            <mySubSubConfig1Element keyAttribute="value2"/>
        </mySubSubConfig1>
        <mySubSubConfig1 keyAttribute="value2">
            <mySubSubConfig1Element keyAttribute="value1"/>
        </mySubSubConfig1>
    </mySubConfig1>
    <mySubConfig2>
        <mySubSubConfig2 keyAttribute="value1">
            <mySubSubConfig2Element keyAttribute="value1"/>
            <mySubSubConfig2Element keyAttribute="value2"/>
        </mySubSubConfig2>
        <mySubSubConfig2 keyAttribute="value2">
            <mySubSubConfig2Element keyAttribute="value1"/>
        </mySubSubConfig2>
    </mySubConfig2>
    <mySubConfig3>
        <mySubSubConfig3 keyAttribute="value1">
            <mySubSubConfig3Element keyAttribute="value1"/>
            <mySubSubConfig3Element keyAttribute="value2"/>
        </mySubSubConfig3>
        <mySubSubConfig3 keyAttribute="value2">
            <mySubSubConfig3Element keyAttribute="value1"/>
        </mySubSubConfig3>
    </mySubConfig3>
</myConfig>

I haven't yet found the magic that would permit this withoug using the old IConfigurationSectionHandler interface. Does anyone know how to do it?

It's ok with me if myConfig and the mySubConfign are ConfigurationSectionGroup or ConfigurationSection.

Also, if it matters, this will be used from web.config.


Solution

  • In your example config file, myConfig would be a class that inherits from ConfigurationSection with three properties named mySubConfig1, mySubConfig2 and mySubConfig3.

    The type of the mySubConfig1 property (as well as 2 and 3) would be a class that inherits from ConfigurationElementCollection, implements IEnumerable<ConfigElement> and is decorated with ConfigurationCollection (where the "AddItemName" property is set to "mySubSubConfig1").

    Below is a complete sample implementation of an approach I used in a production deployment. Be sure to include the System.Configuration assembly. (It's a bit confusing because the System.Configuration namespace is defined in other assmeblies, but you must include the System.Configuration assembly to use the code below.)

    Here are the custom configuration classes:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Configuration;
    
    namespace ConfigTest {
        class CustomConfigSection : ConfigurationSection {
    
            [ConfigurationProperty( "ConfigElements", IsRequired = true )]
            public ConfigElementsCollection ConfigElements {
                get {
                    return base["ConfigElements"] as ConfigElementsCollection;
                }
            }
    
        }
    
        [ConfigurationCollection( typeof( ConfigElement ), AddItemName = "ConfigElement" )]
        class ConfigElementsCollection : ConfigurationElementCollection, IEnumerable<ConfigElement> {
    
            protected override ConfigurationElement CreateNewElement() {
                return new ConfigElement();
            }
    
            protected override object GetElementKey( ConfigurationElement element ) {
                var l_configElement = element as ConfigElement;
                if ( l_configElement != null )
                    return l_configElement.Key;
                else
                    return null;
            }
    
            public ConfigElement this[int index] {
                get {
                    return BaseGet( index ) as ConfigElement;
                }
            }
    
            #region IEnumerable<ConfigElement> Members
    
            IEnumerator<ConfigElement> IEnumerable<ConfigElement>.GetEnumerator() {
                return ( from i in Enumerable.Range( 0, this.Count )
                         select this[i] )
                        .GetEnumerator();
            }
    
            #endregion
        }
    
        class ConfigElement : ConfigurationElement {
    
            [ConfigurationProperty( "key", IsKey = true, IsRequired = true )]
            public string Key {
                get {
                    return base["key"] as string;
                }
                set {
                    base["key"] = value;
                }
            }
    
            [ConfigurationProperty( "SubElements" )]
            public ConfigSubElementsCollection SubElements {
                get {
                    return base["SubElements"] as ConfigSubElementsCollection;
                }
            }
    
        }
    
        [ConfigurationCollection( typeof( ConfigSubElement ), AddItemName = "ConfigSubElement" )]
        class ConfigSubElementsCollection : ConfigurationElementCollection, IEnumerable<ConfigSubElement> {
    
            protected override ConfigurationElement CreateNewElement() {
                return new ConfigSubElement();
            }
    
            protected override object GetElementKey( ConfigurationElement element ) {
                var l_configElement = element as ConfigSubElement;
                if ( l_configElement != null )
                    return l_configElement.Key;
                else
                    return null;
            }
    
            public ConfigSubElement this[int index] {
                get {
                    return BaseGet( index ) as ConfigSubElement;
                }
            }
    
            #region IEnumerable<ConfigSubElement> Members
    
            IEnumerator<ConfigSubElement> IEnumerable<ConfigSubElement>.GetEnumerator() {
                return ( from i in Enumerable.Range( 0, this.Count )
                         select this[i] )
                        .GetEnumerator();
            }
    
            #endregion
        }
    
        class ConfigSubElement : ConfigurationElement {
    
            [ConfigurationProperty( "key", IsKey = true, IsRequired = true )]
            public string Key {
                get {
                    return base["key"] as string;
                }
                set {
                    base["key"] = value;
                }
            }
    
        }
    
    
    }
    

    Here's the App.config file:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <configSections>
        <section name="CustomConfigSection" type="ConfigTest.CustomConfigSection,ConfigTest" />
      </configSections>
    
      <CustomConfigSection>
        <ConfigElements>
          <ConfigElement key="Test1">
            <SubElements>
              <ConfigSubElement key="-SubTest1.1" />
              <ConfigSubElement key="-SubTest1.2" />
            </SubElements>
          </ConfigElement>
          <ConfigElement key="Test2">
            <SubElements>
              <ConfigSubElement key="-SubTest2.1" />
              <ConfigSubElement key="-SubTest2.2" />
            </SubElements>
          </ConfigElement>
        </ConfigElements>
      </CustomConfigSection>
    
    </configuration>
    

    Finally, here's the code which accesses and uses the config file:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Configuration;
    
    namespace ConfigTest {
        class Program {
            static void Main( string[] args ) {
    
                var l_configSettings = (CustomConfigSection) ConfigurationManager.GetSection( "CustomConfigSection" );
    
                foreach ( var l_element in l_configSettings.ConfigElements.AsEnumerable() ) {
                    Console.WriteLine( l_element.Key );
    
                    foreach ( var l_subElement in l_element.SubElements.AsEnumerable() ) {
                        Console.WriteLine( l_subElement.Key );
                    }
    
                }
    
                Console.WriteLine( "Press any key..." );
                Console.ReadKey( true );
    
            }
        }
    }
    

    A lighter-weight alternative was written by Sunil Singh on his blog:
    http://blogs.quovantis.com/net-creating-a-custom-configuration-section-that-contains-a-collection-of-collections/