5 minute read

Update (Feb 10, 2016): I found a NuGet package called simple-config that allows you to dynamically bind a section in your web/app.config file to a strongly typed class without having to write all of the boiler-plate code that I show here. This may be an easier solution for you than going through the code I show below in this post.

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.

Due to the popularity of this post, I have created a sample solution that shows the full implementation of both of the methods mentioned below.

Download the source code example

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:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="ConnectionManagerDatabaseServers" type="System.Configuration.NameValueSectionHandler" />
    </configSections>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <ConnectionManagerDatabaseServers>
        <add key="localhost" value="localhost" />
        <add key="Dev" value="Dev.MyDomain.local" />
        <add key="Test" value="Test.MyDomain.local" />
        <add key="Live" value="Prod.MyDomain.com" />
    </ConnectionManagerDatabaseServers>
</configuration>

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

string devUrl = string.Empty;
var connectionManagerDatabaseServers = ConfigurationManager.GetSection("ConnectionManagerDatabaseServers") as NameValueCollection;
if (connectionManagerDatabaseServers != null)
{
    devUrl = connectionManagerDatabaseServers["Dev"].ToString();
}

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:

// Grab the Environments listed in the App.config and add them to our list.
var connectionManagerDatabaseServers = ConfigurationManager.GetSection("ConnectionManagerDatabaseServers") as NameValueCollection;
if (connectionManagerDatabaseServers != null)
{
    foreach (var serverKey in connectionManagerDatabaseServers.AllKeys)
    {
        string serverValue = connectionManagerDatabaseServers.GetValues(serverKey).FirstOrDefault();
        AddDatabaseServer(serverValue);
    }
}

And here we just assume that the AddDatabaseServer() function adds the given string to some list of strings.

One thing to note is that in the app.config file, <configSections> must be the first thing to appear in the <configuration> section, otherwise an error will be thrown at runtime. Also, the ConfigurationManager class is in the System.Configuration namespace, so be sure you have

using System.Configuration

at the top of your C# files, as well as the System.Configuration assembly included in your project’s references.

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:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="ConnectionManagerDataSection" type="ConnectionManagerUpdater.Data.Configuration.ConnectionManagerDataSection, ConnectionManagerUpdater" />
    </configSections>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <ConnectionManagerDataSection>
        <ConnectionManagerEndpoints>
            <add name="Development" address="Dev.MyDomain.local" useSSL="false" />
            <add name="Test" address="Test.MyDomain.local" useSSL="true" />
            <add name="Live" address="Prod.MyDomain.com" useSSL="true" securityGroupsAllowedToSaveChanges="ConnectionManagerUsers" />
        </ConnectionManagerEndpoints>
    </ConnectionManagerDataSection>
</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) and 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:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectionManagerUpdater.Data.Configuration
{
    public class ConnectionManagerDataSection : ConfigurationSection
    {
        /// <summary>
        /// The name of this section in the app.config.
        /// </summary>
        public const string SectionName = "ConnectionManagerDataSection";

        private const string EndpointCollectionName = "ConnectionManagerEndpoints";

        [ConfigurationProperty(EndpointCollectionName)]
        [ConfigurationCollection(typeof(ConnectionManagerEndpointsCollection), AddItemName = "add")]
        public ConnectionManagerEndpointsCollection ConnectionManagerEndpoints { get { return (ConnectionManagerEndpointsCollection)base[EndpointCollectionName]; } }
    }

    public class ConnectionManagerEndpointsCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new ConnectionManagerEndpointElement();
        }

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

    public class ConnectionManagerEndpointElement : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get { return (string)this["name"]; }
            set { this["name"] = value; }
        }

        [ConfigurationProperty("address", IsRequired = true)]
        public string Address
        {
            get { return (string)this["address"]; }
            set { this["address"] = value; }
        }

        [ConfigurationProperty("useSSL", IsRequired = false, DefaultValue = false)]
        public bool UseSSL
        {
            get { return (bool)this["useSSL"]; }
            set { this["useSSL"] = value; }
        }

        [ConfigurationProperty("securityGroupsAllowedToSaveChanges", IsRequired = false)]
        public string SecurityGroupsAllowedToSaveChanges
        {
            get { return (string)this["securityGroupsAllowedToSaveChanges"]; }
            set { this["securityGroupsAllowedToSaveChanges"] = value; }
        }
    }
}

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 required 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:

// Grab the Environments listed in the App.config and add them to our list.
var connectionManagerDataSection = ConfigurationManager.GetSection(ConnectionManagerDataSection.SectionName) as ConnectionManagerDataSection;
if (connectionManagerDataSection != null)
{
    foreach (ConnectionManagerEndpointElement endpointElement in connectionManagerDataSection.ConnectionManagerEndpoints)
    {
        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() } };
        AddEndpoint(endpoint);
    }
}

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!

Comments

Michael

I believe; this row: [ConfigurationCollection(typeof(ConnectionManagerEndpointsCollection), AddItemName = “add”)]

Should be placex over the Coolection class and NOT the Section class, besides from that, I thank you for your fine article.

Richard

Great article, thanks. Just wonder if you can show the code for the AddEndPoint method called in the last example?

deadlydog

@Richard Hey Richard, I’ve just updated this post to include a full source code example showing how to fully implement the methods mentioned in this post. Take a look and you should find all of the details that you need.

Ankit

Thank you so much. thanks for this article. I really needed this code in this article. Thank you * 1000

Vamsi

Hi , When I am debugging the attached source code, I am getting this error {“The type initializer for ‘AppConfigExample.AdvancedExamplesData.Endpoints’ threw an exception.”}.

Same happening for Environments and DataCenters.

Can you help me in solving this pls?

I am using VS 2010 and Target Frame Work: .NET Framework4 Client Profile

Vamsi

In Program.cs till Console.WriteLine(“\nEndpoints:”); its working fine.

First run of foreach is failing. foreach (var endpoint in AdvancedExamplesData.Endpoints.EndpointsList) { .. } It is failing in this part ,’AdvancedExamplesData.Endpoints.EndpointsList’ of foreach loop with error “The type initializer for ‘AppConfigExample.AdvancedExamplesData.Endpoints’ threw an exception.”

Vamsi

in EndPoint.cs file.. i replaced

//var customSection = ConfigurationManager.GetSection(CustomConfigurationSection.SectionName) as CustomConfigurationSection;

to below and that works.. var test = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); var customSection = test.GetSection(CustomConfigurationSection.SectionName) as CustomConfigurationSection;

Looks like this is a bug in .NET Framework 4.

And sorry to mention lately.. Thank a lot for a quick reposne and wonderful explantion and code.

Gopinath

You are none other than my well wisher. Thanks a lot for such a excellent.smart method of custom config sections.

Zak

Firstly, this is a great article so please don’t take offence at what I am about to write. My problem is that configuration in .Net is an afterthought, neglected and a disaster. This example is great in that you could use it to define a custom and meaningful pointer to better configuration mechanisms.

All that configuration should be is pointers to the locations of configuration. This is synonymous with having a database connection string that points to data (which is itself, a form of configuration).

A huge amount of code is needed just to produce something as simple as create a configuration section in a .net config file. I admire this article for it’s effort but would you be happy creating multiple classes just to set up some configuration sections? For me, the best strategy for managing configuration is to use something like ConfigR. you can declare objects inside text files that are direct mirrors of the objects you intend to use.

There is poorly formed attribute based xml in this example, another bane of .Net configuration, so it is hard to understand the structure of the intended configuration - let alone the intended use of the configuration so whilst we may know that something is a connection string, connection string to what? Quickly, there ends up being redundant configuration that serves no purpose.

The final nail in the coffin of .Net configuration is that once you have gone through the process of implementing configurationElement and configurationSections, carefully hard coding the keys to the configuration values, inside your code, somewhere you have to map the configuration items to arguments supplied to various classes etc.

I am completing an MVC project and I will publish an alternative on something like CodeProject on how configuration should be done but please try and think about how configuration should be implemented.

Michael

First of all, thank you so much for this post. It has been very useful.

I do have one question. In your sample application, again thank you so much for providing a working sample application, when you add the training environment, does that get persisted to the app.config file? If so, where does that live? (I’ve looked at the AppConfigExample.exe.config in the bin directory, but it doesn’t appear to have been updated.) If it doesn’t get persisted back, how would you implement something along those lines.

Thanks again for a great article!

deadlydog

@Michael Thanks Michael. The Training environment is not persisted back to the AppConfigExample.exe.config file; it is just retained in memory. So the next time you run your app it will not have the Training environment. If you want it persisted I guess you would need to write the added values to a file somewhere, and then on load have your app check that file for any additional values to add to the list. You could perhaps try writing directly to the AppConfigExample.exe.config file, but I’m not sure if that’s the best idea as if there’s a bug you could end up mangling the .config file and not be able to launch the app anymore.

Rick O'Shea

Which begs the question… why does it take pages of boilerplate to read in a key-value list? This really does highlight just how heavy (and needlessly so) .NET can be. Who would guess it would take more than 10 lines of code (because in NodeJS for example, it would be).

zak willis

Hello, thanks for this post. I am in Switzerland on holiday with only my mobile phone. I am going to be writing a book which will involve lambasting before resolving one of the worst elements and biggest jokes in static code development - configuration. I have used simpleconfig for a simple purpose, to be able to avoid having to replicate keys across different application and test project. If you rely solely upon simple config a multiproject solution will end up with huge config files. It is barely acceptable that 3rd party libraries put massive sections inside config files but to have references to configuration manager but to try and mirror that just to try and be like these guys is insane. My solution is to use simpleconfig to get a few keys but to use configr to return configured classes instead. If we think about an application it only exists because of state that entails data and data can be thought of as being dynamic or static. Configuration is just a slower moving set of pointers to faster moving data. With all this information, the least any application deserves is to get data in the format it expects and not spending countless time referring to keys and sections throughout the application. My solution is to store dictionary / and to inject iconfigurator with a method that allows for a dictionary lookup to return that poco. The advantage of this approach is that we could swap the concrete disk based config poco to a completely different source altogether by changing the bindings. In my world, the only thing config files contain are pointers to .net config files for use by simpleconfig and text files to point to poco implementation. I will try and put some code up at some point but it isn’t rocket science, just creating a few classes to expose data to applications in the format they recognise rather than jumping through hoops to add custom config sections or mapping keys.

Leave a Comment

Your email address will not be published. Required fields are marked *

Loading...