Search code examples
c#configurationconfiguration-filesconfigurationsection

Correct implementation of a custom config section with nested collections?


In a web application, I want to be able to define some mapping using a config section like this:

<configuration>
    <configSections>
        <sectionGroup name="MyCustomer">
            <section name="CatalogMappings" type="MyCustom.MyConfigSection" />
        </sectionGroup>
    </configSections>
    <MyCustomer>
        <catalogMappings>
            <catalog name="toto">
                <mapping value="1" displayText="titi" />
                <mapping value="2" displayText="tata" />
            </catalog>
            <catalog name="toto2">
                <mapping value="1" displayText="titi2" />
                <mapping value="2" displayText="tata2" />
            </catalog>
        </catalogMappings>
    </MyCustomer>
</configuration>

I'm struggling to achieve this goal, especially when defining my collection of collections. What are the classes that I need to implement to achieve this?

Currently, I have:

public class CatalogMappingSection : System.Configuration.ConfigurationSection
{
    public class Mapping : ConfigurationElement
    {
        [ConfigurationProperty("externalKey")]
        public string ExternalKey { get; set; }
        [ConfigurationProperty("displayText", IsRequired=true)]
        public string DisplayText { get; set; }
        [ConfigurationProperty("value", IsRequired=true, IsKey=true)]
        public int Value { get; set; }
    }

    public class Catalog : ConfigurationElementCollection
    {
        [ConfigurationProperty("name", IsRequired=true, IsKey=true)]
        public string Name { get; set; }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Mapping();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Mapping)element).Value;
        }
    }

    public class CatalogCollection : ConfigurationElementCollection
    {
        [ConfigurationProperty("catalog")]
        [ConfigurationCollection(typeof(Catalog))]
        public Catalog CatalogMappingCollection
        {
            get
            {
                return (Catalog)base["catalog"];
            }
        }

        protected override ConfigurationElement CreateNewElement()
        {
            return new Catalog();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((Catalog)element).Name;
        }
    }

    [ConfigurationProperty("catalogMappings")]
    [ConfigurationCollection(typeof(CatalogCollection))]
    public CatalogCollection CatalogMappings
    {
        get
        {
            return (CatalogCollection)base["catalogMappings"];
        }
    }
}

But, this is not working as expected.


Solution

  • I finally found this guy's example. It was coded and worked right out of the box.

    http://manyrootsofallevilrants.blogspot.com/2011/07/nested-custom-configuration-collections.html

    I am going to paste the code here......only because I cannot stand it when someone says "Your answer is here", and the link is dead.

    Please try his website first, and leave a "thank you" if it works.

    Note, I also have a "Usa-States and Usa-Counties" example (an answer to THIS SOF question) if you want a "more well known" (object model) example.

    using System;
    using System.Configuration;
     
    namespace SSHTunnelWF
    {
        public class TunnelSection : ConfigurationSection
        {
            [ConfigurationProperty("", IsDefaultCollection = true)]  
            public HostCollection Tunnels
            {
                get
                {
                    HostCollection hostCollection = (HostCollection)base[""];
                    return hostCollection;                
                }
            }
        }
     
        public class HostCollection : ConfigurationElementCollection
        {
            public HostCollection()
            {
                HostConfigElement details = (HostConfigElement)CreateNewElement();
                if (details.SSHServerHostname != "")
                {
                    Add(details);
                }
            }
     
            public override ConfigurationElementCollectionType CollectionType
            {
                get
                {
                    return ConfigurationElementCollectionType.BasicMap;
                }
            }
     
            protected override ConfigurationElement CreateNewElement()
            {
                return new HostConfigElement();
            }
     
            protected override Object GetElementKey(ConfigurationElement element)
            {
                return ((HostConfigElement)element).SSHServerHostname;
            }
     
            public HostConfigElement this[int index]
            {
                get
                {
                    return (HostConfigElement)BaseGet(index);
                }
                set
                {
                    if (BaseGet(index) != null)
                    {
                        BaseRemoveAt(index);
                    }
                    BaseAdd(index, value);
                }
            }
     
            new public HostConfigElement this[string name]
            {
                get
                {
                    return (HostConfigElement)BaseGet(name);
                }
            }
     
            public int IndexOf(HostConfigElement details)
            {
                return BaseIndexOf(details);
            }
     
            public void Add(HostConfigElement details)
            {
                BaseAdd(details);
            }
    
            protected override void BaseAdd(ConfigurationElement element)
            {
                BaseAdd(element, false);
            }
     
            public void Remove(HostConfigElement details)
            {
                if (BaseIndexOf(details) >= 0)
                    BaseRemove(details.SSHServerHostname);
            }
     
            public void RemoveAt(int index)
            {
                BaseRemoveAt(index);
            }
     
            public void Remove(string name)
            {
                BaseRemove(name);
            }
     
            public void Clear()
            {
                BaseClear();
            }
     
            protected override string ElementName
            {
                get { return "host"; }
            }
        }
     
        public class HostConfigElement:ConfigurationElement
        {
            [ConfigurationProperty("SSHServerHostname", IsRequired = true, IsKey = true)]
            [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
            public string SSHServerHostname
            {
                get { return (string)this["SSHServerHostname"]; }
                set { this["SSHServerHostname"] = value; }
            }
     
            [ConfigurationProperty("username", IsRequired = true)]
            [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
            public string Username
            {
                get { return (string)this["username"]; }
                set { this["username"] = value; }
            }
    
            [ConfigurationProperty("SSHport", IsRequired = true, DefaultValue = 22)]
            [IntegerValidator(MinValue = 1, MaxValue = 65536)]
            public int SSHPort
            {
                get { return (int)this["SSHport"]; }
                set { this["SSHport"] = value; }
            }
     
            [ConfigurationProperty("password", IsRequired = false)]
            public string Password
            {
                get { return (string)this["password"]; }
                set { this["password"] = value; }
            }
     
            [ConfigurationProperty("privatekey", IsRequired = false)]
            public string Privatekey
            {
                get { return (string)this["privatekey"]; }
                set { this["privatekey"] = value; }
            }
     
            [ConfigurationProperty("privatekeypassphrase", IsRequired = false)]
            public string Privatekeypassphrase
            {
                get { return (string)this["privatekeypassphrase"]; }
                set { this["privatekeypassphrase"] = value; }
            }
     
            [ConfigurationProperty("tunnels", IsDefaultCollection = false)]
            public TunnelCollection Tunnels
            {
                get { return (TunnelCollection)base["tunnels"]; }
            }
        }
     
        public class TunnelCollection : ConfigurationElementCollection
        {
            public new TunnelConfigElement this[string name]
            {
                get
                {
                    if (IndexOf(name) < 0) return null;
                    return (TunnelConfigElement)BaseGet(name);
                }
            }
     
            public TunnelConfigElement this[int index]
            {
                get { return (TunnelConfigElement)BaseGet(index); }
            }
     
            public int IndexOf(string name)
            {
                name = name.ToLower();
     
                for (int idx = 0; idx < base.Count; idx++)
                {
                    if (this[idx].Name.ToLower() == name)
                        return idx;
                }
                return -1;
            }
     
            public override ConfigurationElementCollectionType CollectionType
            {
                get { return ConfigurationElementCollectionType.BasicMap; }
            }
     
            protected override ConfigurationElement CreateNewElement()
            {
                return new TunnelConfigElement();
            }
     
            protected override object GetElementKey(ConfigurationElement element)
            {
                return ((TunnelConfigElement)element).Name;
            }
     
            protected override string ElementName
            {
                get { return "tunnel"; }
            }
        }
     
        public class TunnelConfigElement : ConfigurationElement
        {        
            public TunnelConfigElement()
            {
            }
     
            public TunnelConfigElement(string name, int localport, int remoteport, string destinationserver)
            {
                this.DestinationServer = destinationserver;
                this.RemotePort = remoteport;
                this.LocalPort = localport;            
                this.Name = name;
            }
     
            [ConfigurationProperty("name", IsRequired = true, IsKey = true, DefaultValue = "")]       
            public string Name
            {
                get { return (string)this["name"]; }
                set { this["name"] = value; }
            }        
     
            [ConfigurationProperty("localport", IsRequired = true, DefaultValue =1)]
            [IntegerValidator(MinValue = 1, MaxValue = 65536)]
            public int LocalPort
            {
                get { return (int)this["localport"]; }
                set { this["localport"] = value; }
            }
     
            [ConfigurationProperty("remoteport", IsRequired = true, DefaultValue =1)]
            [IntegerValidator(MinValue = 1, MaxValue = 65536)]
            public int RemotePort
            {
                get { return (int)this["remoteport"]; }
                set { this["remoteport"] = value; }
            }
     
            [ConfigurationProperty("destinationserver", IsRequired = true)]
            [StringValidator(InvalidCharacters = "  ~!@#$%^&*()[]{}/;’\"|\\")]
            public string DestinationServer
            {
                get { return (string)this["destinationserver"]; }
                set { this["destinationserver"] = value; }
            }
        }
    }
    

    And the configuration code

     <?xml version="1.0"?>
     <configuration>
       <configSections>
         <section name="TunnelSection" type="SSHTunnelWF.TunnelSection,SSHTunnelWF" />
       </configSections>
       <TunnelSection>
         <host SSHServerHostname="tsg.edssdn.net" username="user" SSHport="22" password="pass" privatekey="" privatekeypassphrase="">
           <tunnels>
             <tunnel name="tfs" localport="8081"  remoteport="8080" destinationserver="tfs2010.dev.com"  />
             <tunnel name="sql" localport="14331"  remoteport="1433" destinationserver="sql2008.dev.com"  />
             <tunnel name="crm2011app" localport="81"  remoteport="80" destinationserver="crm2011betaapp.dev.com"  />
           </tunnels>
         </host>
         <host SSHServerHostname="blade16" username="root" SSHport="22"  password="pass" privatekey="" privatekeypassphrase="">
          <tunnels>
            <tunnel name="vnc" localport="5902"  remoteport="5902" destinationserver="blade1.dev.com" />
          </tunnels>
         </host>
       </TunnelSection>
     </configuration>
    

    And then the "call"

    TunnelSection tunnels = ConfigurationManager.GetSection("TunnelSection") as TunnelSection