90

How do I create a custom app.config section that is just a simple list of add elements?

I have found a few examples (e.g. How to create custom config section in app.config?) for custom sections that look like this:

<RegisterCompanies>
  <Companies>
    <Company name="Tata Motors" code="Tata"/>
    <Company name="Honda Motors" code="Honda"/>
  </Companies>
</RegisterCompanies>

But how do I avoid the extra collection element ("Companies") so that it looks the same as the appSettings and connectionStrings sections? In other words, I'd like:

<registerCompanies>
  <add name="Tata Motors" code="Tata"/>
  <add name="Honda Motors" code="Honda"/>
</registerCompanies>
Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
Joe Daley
  • 45,356
  • 15
  • 65
  • 64

4 Answers4

119

Full example with code based on OP config file:

<configuration>
    <configSections>
        <section name="registerCompanies" 
                 type="My.MyConfigSection, My.Assembly" />
    </configSections>
    <registerCompanies>
        <add name="Tata Motors" code="Tata"/>
        <add name="Honda Motors" code="Honda"/>
    </registerCompanies>
</configuration>

Here is the sample code to implement a custom config section with collapsed collection

using System.Configuration;
namespace My {
public class MyConfigSection : ConfigurationSection {
    [ConfigurationProperty("", IsRequired = true, IsDefaultCollection = true)]
    public MyConfigInstanceCollection Instances {
        get { return (MyConfigInstanceCollection)this[""]; }
        set { this[""] = value; }
    }
}
public class MyConfigInstanceCollection : ConfigurationElementCollection {
    protected override ConfigurationElement CreateNewElement() {
        return new MyConfigInstanceElement();
    }

    protected override object GetElementKey(ConfigurationElement element) {
        //set to whatever Element Property you want to use for a key
        return ((MyConfigInstanceElement)element).Name;
    }
}

public class MyConfigInstanceElement : ConfigurationElement {
    //Make sure to set IsKey=true for property exposed as the GetElementKey above
    [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
    public string Name {
        get { return (string) base["name"]; }
        set { base["name"] = value; }
    }

    [ConfigurationProperty("code", IsRequired = true)]
    public string Code {
        get { return (string) base["code"]; }
        set { base["code"] = value; }
    } } }

Here is an example of how to access the configuration information from code.

var config = ConfigurationManager.GetSection("registerCompanies") 
                 as MyConfigSection;

Console.WriteLine(config["Tata Motors"].Code);
foreach (var e in config.Instances) { 
   Console.WriteLine("Name: {0}, Code: {1}", e.Name, e.Code); 
}
Jay Walker
  • 4,654
  • 5
  • 47
  • 53
  • @Jay Walker how do you go about accessing the item that you need, ie :- config.Instances["Tata Motors"] is it possible to do this? – Simon May 27 '14 at 12:50
  • @Simon, see updates to the answer for example usage. – Jay Walker May 30 '14 at 03:52
  • 2
    Should point out the `` should be right after `` tag for it to work! – Vedran Kopanja Dec 25 '14 at 23:01
  • 3
    Should also point out that – Steve's a D Jul 09 '15 at 15:19
  • @SteveG, not sure what you mean. This is a working answer with .net3.5. – Jay Walker Jul 09 '15 at 20:22
  • @JayWalker, Sorry,this answer is 100% correct. I was just saying I tried using custom list items, instead of – Steve's a D Jul 09 '15 at 22:27
  • 9
    AFAIK - this code "config["Tata Motors"]" will not compile b/c the indexer of config is protected internal. you will have to find a way to enumerate the items in the collection on your own. – CedricB Nov 03 '15 at 17:37
  • @Glen it looks like you have a capitalization problem EL in ELementName. If that doesn't work you need to open a new question as your issue looks to be related to the loader finding your assembly. 4 properties should not be a problem. you just need to define a ConfigurationProperty for each one. – Jay Walker Jun 22 '17 at 20:15
  • 2
    @JayWalker all good. The "My.MyConfiguration, My.Assembly" in your example for the section type throw me. I just had to use "MyAssembly.MyConfiguration, MyAssembly" for what I was attempting. – Glen Jun 26 '17 at 02:03
  • type="My.MyConfigSection, My" worked for me in configSections. – Himalaya Garg Sep 17 '20 at 13:02
43

No custom configuration section necessary.

app.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="YourAppSettings" type="System.Configuration.AppSettingsSection, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  </configSections>
  <!-- value attribute is optional. omit if you just want a list of 'keys' -->
  <YourAppSettings>
    <add key="one" value="1" />
    <add key="two" value="2"/>
    <add key="three" value="3"/>
    <add key="duplicate" value="aa"/>
    <add key="duplicate" value="bb"/>
  </YourAppSettings>
</configuration>

Retrieve values

// AppSettingsSection can be cast to a NameValueCollection
NameValueCollection settingCollection = 
(NameValueCollection)ConfigurationManager.GetSection("YourAppSettings");

// An array of the keys. No Duplicates
// { "one", "two", "three", "duplicate" }
string[] allKeys = settingCollection.AllKeys; 
    
// key/value pairs
// one : 1
// two : 2
// three : 3
// duplicate : bb 
foreach (string key in allKeys)
{
   Console.WriteLine(key + " : " + settingCollection[key]);
}

// Duplicates behavior
var items = settingCollection.Count;
Debug.Assert(items == 4); // no duplicates. Last element wins.
Debug.Assert(settingCollection["duplicate"] == "bb");
JJS
  • 6,431
  • 1
  • 54
  • 70
  • 1
    I guess it doesn't strictly answer the OP's question, but I think it's a valid solution, and a much simpler one. At the very least it helped me! – styl0r Jul 12 '16 at 18:10
  • 2
    @styl0r you're right. it doesn't *strictly* answer it. If you have to use the attributes name/code instead of my solution key/value, you'd have to use a truly custom section. However, I assume you're in control of the config file, and have better things to do than make a custom class. – JJS Jul 13 '16 at 16:02
  • 4
    Very simple and clean! No need for any additional custom section/element bloatware. – Ondřej Aug 20 '18 at 07:49
  • 2
    You can also update to Version=4.0.0.0 if you'd like by just changing the version number. This is the best answer imo if you just need additional simple lists. The same can be done for "System.Configuration.ConnectionStringsSection" as well, though duplicates are handled slightly differently than app settings. – Sharpiro Mar 18 '19 at 14:03
  • @Sharpiro were you having issues with the assembly version? I thought the assembly binding would have been in-pace, even for newer versions of the framework. – JJS Mar 19 '19 at 16:02
  • @JJS no it worked fine. I just thought it made more since to use the version that was already being referenced by my project. – Sharpiro Mar 20 '19 at 17:09
  • @Sharpiro Good to know it wasn't an issue – JJS Mar 20 '19 at 17:19
22

Based on Jay Walker's answer above, this is a complete working example that adds the ability to do the indexing:

<configuration>
    <configSections>
        <section name="registerCompanies" 
                 type="My.MyConfigSection, My.Assembly" />
    </configSections>
    <registerCompanies>
        <add name="Tata Motors" code="Tata"/>
        <add name="Honda Motors" code="Honda"/>
    </registerCompanies>
</configuration>

Here is the sample code to implement a custom config section with collapsed collection

using System.Configuration;
using System.Linq;
namespace My
{
   public class MyConfigSection : ConfigurationSection
   {
      [ConfigurationProperty("", IsRequired = true, IsDefaultCollection = true)]
      public MyConfigInstanceCollection Instances
      {
         get { return (MyConfigInstanceCollection)this[""]; }
         set { this[""] = value; }
      }
   }
   public class MyConfigInstanceCollection : ConfigurationElementCollection
   {
      protected override ConfigurationElement CreateNewElement()
      {
         return new MyConfigInstanceElement();
      }

      protected override object GetElementKey(ConfigurationElement element)
      {
         //set to whatever Element Property you want to use for a key
         return ((MyConfigInstanceElement)element).Name;
      }

      public new MyConfigInstanceElement this[string elementName]
      {
         get
         {
            return this.OfType<MyConfigInstanceElement>().FirstOrDefault(item => item.Name == elementName);
         }
      }
   }

   public class MyConfigInstanceElement : ConfigurationElement
   {
      //Make sure to set IsKey=true for property exposed as the GetElementKey above
      [ConfigurationProperty("name", IsKey = true, IsRequired = true)]
      public string Name
      {
         get { return (string)base["name"]; }
         set { base["name"] = value; }
      }

      [ConfigurationProperty("code", IsRequired = true)]
      public string Code
      {
         get { return (string)base["code"]; }
         set { base["code"] = value; }
      }
   }
}

Here is an example of how to access the configuration information from code.

MyConfigSection config = 
   ConfigurationManager.GetSection("registerCompanies") as MyConfigSection;

Console.WriteLine(config.Instances["Honda Motors"].Code);
foreach (MyConfigInstanceElement e in config.Instances)
{
   Console.WriteLine("Name: {0}, Code: {1}", e.Name, e.Code);
}
Community
  • 1
  • 1
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
8

Based on the answer by Jay Walker, accessing the elements needs to be done by iterating through the "Instances" collection. ie.

var config = ConfigurationManager.GetSection("registerCompanies") 
                 as MyConfigSection;

foreach (MyConfigInstanceElement e in config.Instances) { 
   Console.WriteLine("Name: {0}, Code: {1}", e.Name, e.Code); 
}
Bonneech
  • 101
  • 1
  • 3