Adding and accessing custom sections in your C# App.config

Posted by deadlydog on Geeks with Blogs See other posts from Geeks with Blogs or by deadlydog
Published on Tue, 25 Sep 2012 21:00:22 GMT Indexed on 2012/09/26 21:38 UTC
Read the original article Hit count: 423

Filed under:

So I recently thought I’d try using the app.config file to specify some data for my application (such as URLs) rather than hard-coding it into my app, which would require a recompile and redeploy of my app if one of our URLs changed.  By using the app.config it allows a user to just open up the .config file that sits beside their .exe file and edit the URLs right there and then re-run the app; no recompiling, no redeployment necessary.

I spent a good few hours fighting with the app.config and looking at examples on Google before I was able to get things to work properly.  Most of the examples I found showed you how to pull a value from the app.config if you knew the specific key of the element you wanted to retrieve, but it took me a while to find a way to simply loop through all elements in a section, so I thought I would share my solutions here.

 

Simple and Easy

The easiest way to use the app.config is to use the built-in types, such as NameValueSectionHandler.  For example, if we just wanted to add a list of database server urls to use in my app, we could do this in the app.config file like so:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <configSections>
   4:         <section name="ConnectionManagerDatabaseServers" type="System.Configuration.NameValueSectionHandler" />
   5:     </configSections>
   6:     <startup>
   7:         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
   8:     </startup>
   9:     <ConnectionManagerDatabaseServers>
  10:         <add key="localhost" value="localhost" />
  11:         <add key="Dev" value="Dev.MyDomain.local" />
  12:         <add key="Test" value="Test.MyDomain.local" />
  13:         <add key="Live" value="Prod.MyDomain.com" />
  14:     </ConnectionManagerDatabaseServers>
  15: </configuration>

 

And then you can access these values in code like so:

   1: string devUrl = string.Empty;
   2: var connectionManagerDatabaseServers = ConfigurationManager.GetSection("ConnectionManagerDatabaseServers") as NameValueCollection;
   3: if (connectionManagerDatabaseServers != null)
   4: {
   5:     devUrl = connectionManagerDatabaseServers["Dev"].ToString();
   6: }

 

Sometimes though you don’t know what the keys are going to be and you just want to grab all of the values in that ConnectionManagerDatabaseServers section.  In that case you can get them all like this:

   1: // Grab the Environments listed in the App.config and add them to our list.
   2: var connectionManagerDatabaseServers = ConfigurationManager.GetSection("ConnectionManagerDatabaseServers") as NameValueCollection;
   3: if (connectionManagerDatabaseServers != null)
   4: {
   5:     foreach (var serverKey in connectionManagerDatabaseServers.AllKeys)
   6:     {
   7:         string serverValue = connectionManagerDatabaseServers.GetValues(serverKey).FirstOrDefault();
   8:         AddDatabaseServer(serverValue);
   9:     }
  10: }

 

And here we just assume that the AddDatabaseServer() function adds the given string to some list of strings.  So this works great, but what about when we want to bring in more values than just a single string (or technically you could use this to bring in 2 strings, where the “key” could be the other string you want to store; for example, we could have stored the value of the Key as the user-friendly name of the url).

 

More Advanced (and more complicated)

So if you want to bring in more information than a string or two per object in the section, then you can no longer simply use the built-in System.Configuration.NameValueSectionHandler type provided for us.  Instead you have to build your own types.  Here let’s assume that we again want to configure a set of addresses (i.e. urls), but we want to specify some extra info with them, such as the user-friendly name, if they require SSL or not, and a list of security groups that are allowed to save changes made to these endpoints.

So let’s start by looking at the app.config:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <configSections>
   4:         <section name="ConnectionManagerDataSection" type="ConnectionManagerUpdater.Data.Configuration.ConnectionManagerDataSection, ConnectionManagerUpdater" />
   5:     </configSections>
   6:     <startup>
   7:         <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
   8:     </startup>
   9:     <ConnectionManagerDataSection>
  10:         <ConnectionManagerEndpoints>
  11:             <add name="Development" address="Dev.MyDomain.local" useSSL="false" />
  12:             <add name="Test" address="Test.MyDomain.local" useSSL="true" />
  13:             <add name="Live" address="Prod.MyDomain.com" useSSL="true" securityGroupsAllowedToSaveChanges="ConnectionManagerUsers" />
  14:         </ConnectionManagerEndpoints>
  15:     </ConnectionManagerDataSection>
  16: </configuration>

 

The first thing to notice here is that my section is now using the type “ConnectionManagerUpdater.Data.Configuration.ConnectionManagerDataSection” (the fully qualified path to my new class I created) “, ConnectionManagerUpdater” (the name of the assembly my new class is in).  Next, you will also notice an extra layer down in the <ConnectionManagerDataSection> which is the <ConnectionManagerEndpoints> element.  This is a new collection class that I created to hold each of the Endpoint entries that are defined.  Let’s look at that code now:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Configuration;
   4: using System.Linq;
   5: using System.Text;
   6: using System.Threading.Tasks;
   7:  
   8: namespace ConnectionManagerUpdater.Data.Configuration
   9: {
  10:     public class ConnectionManagerDataSection : ConfigurationSection
  11:     {
  12:         /// <summary>
  13:         /// The name of this section in the app.config.
  14:         /// </summary>
  15:         public const string SectionName = "ConnectionManagerDataSection";
  16:         
  17:         private const string EndpointCollectionName = "ConnectionManagerEndpoints";
  18:  
  19:         [ConfigurationProperty(EndpointCollectionName)]
  20:         [ConfigurationCollection(typeof(ConnectionManagerEndpointsCollection), AddItemName = "add")]
  21:         public ConnectionManagerEndpointsCollection ConnectionManagerEndpoints { get { return (ConnectionManagerEndpointsCollection)base[EndpointCollectionName]; } }
  22:     }
  23:  
  24:     public class ConnectionManagerEndpointsCollection : ConfigurationElementCollection
  25:     {
  26:         protected override ConfigurationElement CreateNewElement()
  27:         {
  28:             return new ConnectionManagerEndpointElement();
  29:         }
  30:     
  31:         protected override object GetElementKey(ConfigurationElement element)
  32:         {
  33:             return ((ConnectionManagerEndpointElement)element).Name;
  34:         }
  35:     }
  36:     
  37:     public class ConnectionManagerEndpointElement : ConfigurationElement
  38:     {
  39:         [ConfigurationProperty("name", IsRequired = true)]
  40:         public string Name
  41:         {
  42:             get { return (string)this["name"]; }
  43:             set { this["name"] = value; }
  44:         }
  45:     
  46:         [ConfigurationProperty("address", IsRequired = true)]
  47:         public string Address
  48:         {
  49:             get { return (string)this["address"]; }
  50:             set { this["address"] = value; }
  51:         }
  52:     
  53:         [ConfigurationProperty("useSSL", IsRequired = false, DefaultValue = false)]
  54:         public bool UseSSL
  55:         {
  56:             get { return (bool)this["useSSL"]; }
  57:             set { this["useSSL"] = value; }
  58:         }
  59:     
  60:         [ConfigurationProperty("securityGroupsAllowedToSaveChanges", IsRequired = false)]
  61:         public string SecurityGroupsAllowedToSaveChanges
  62:         {
  63:             get { return (string)this["securityGroupsAllowedToSaveChanges"]; }
  64:             set { this["securityGroupsAllowedToSaveChanges"] = value; }
  65:         }
  66:     }
  67: }

 

So here the first class we declare is the one that appears in the <configSections> element of the app.config.  It is ConnectionManagerDataSection and it inherits from the necessary System.Configuration.ConfigurationSection class.  This class just has one property (other than the expected section name), that basically just says I have a Collection property, which is actually a ConnectionManagerEndpointsCollection, which is the next class defined. 

The ConnectionManagerEndpointsCollection class inherits from ConfigurationElementCollection and overrides the requied fields.  The first tells it what type of Element to create when adding a new one (in our case a ConnectionManagerEndpointElement), and a function specifying what property on our ConnectionManagerEndpointElement class is the unique key, which I’ve specified to be the Name field.

The last class defined is the actual meat of our elements.  It inherits from ConfigurationElement and specifies the properties of the element (which can then be set in the xml of the App.config).  The “ConfigurationProperty” attribute on each of the properties tells what we expect the name of the property to correspond to in each element in the app.config, as well as some additional information such as if that property is required and what it’s default value should be.

Finally, the code to actually access these values would look like this:

   1: // Grab the Environments listed in the App.config and add them to our list.
   2: var connectionManagerDataSection = ConfigurationManager.GetSection(ConnectionManagerDataSection.SectionName) as ConnectionManagerDataSection;
   3: if (connectionManagerDataSection != null)
   4: {
   5:     foreach (ConnectionManagerEndpointElement endpointElement in connectionManagerDataSection.ConnectionManagerEndpoints)
   6:     {
   7:         var endpoint = new ConnectionManagerEndpoint() { Name = endpointElement.Name, ServerInfo = new ConnectionManagerServerInfo() { Address = endpointElement.Address, UseSSL = endpointElement.UseSSL, SecurityGroupsAllowedToSaveChanges = endpointElement.SecurityGroupsAllowedToSaveChanges.Split(',').Where(e => !string.IsNullOrWhiteSpace(e)).ToList() } };
   8:         AddEndpoint(endpoint);
   9:     }
  10: }

This looks very similar to what we had before in the “simple” example.  The main points of interest are that we cast the section as ConnectionManagerDataSection (which is the class we defined for our section) and then iterate over the endpoints collection using the ConnectionManagerEndpoints property we created in the ConnectionManagerDataSection class.

 

Also, some other helpful resources around using app.config that I found (and for parts that I didn’t really explain in this article) are:

How do you use sections in C# 4.0 app.config? (Stack Overflow) <== Shows how to use Section Groups as well, which is something that I did not cover here, but might be of interest to you.

How to: Create Custom Configuration Sections Using Configuration Section (MSDN)

ConfigurationSection Class (MSDN)

ConfigurationCollectionAttribute Class (MSDN)

ConfigurationElementCollection Class (MSDN)

 

I hope you find this helpful.  Feel free to leave a comment.  Happy Coding!

© Geeks with Blogs or respective owner