Click here to Skip to main content
15,878,970 members
Articles / Programming Languages / C#
Tip/Trick

Simple ConfigurationManager for .NET

Rate me:
Please Sign up or sign in to vote.
3.00/5 (2 votes)
15 Oct 2015CPOL4 min read 10.7K   10   2
A configuration management component for small and middle-sized projects

Introduction

The solution presented here is a simple generic configuration manager that can be used in middle-size projects. I could not find any similar solution on the internet.

Background

The standard ConfigurationManager class provided by .NET was too flat and too simple for needs of my private and work-related projects. From OOP point of view, I found it wrong to allow all my components to access the same configuration data and to mix configuration of parent and children and grandchildren in same place. In bigger projects, it usually ended up in a mess. Creating custom sections in app.config was not satisfying to me as well, also arranging IOC frameworks to deal with that was not the best option to me. These were reasons why I decided to move my configuration to another structure.

Basic Concept

What I decided to create is a configuration manager that will initialize itself using XML string. Basic structure of my configuration XML looks like this:

XML
<configuration name="">
    <settings>
        <setting key="" value="" />
        <setting key="" value="" />
        <setting key="" value="" />
    </settings>
    <customxml></customxml>
    <children></children>
</configuration>

The root is configuration section. Configuration has a name, which is some kind of identifier. This is especially useful when it comes to dealing with more configuration nodes.

The "settings" node contains settings elements which are basically key-value pairs that contain our typical configuration data.

The "customXml" node can store any XML, that cannot be parsed by ConfigurationManager. This is for cases when component needs its own custom XML and can handle parsing it by itself.

And finally - the children node. This is node that (may) contains configurations nodes inside. So the same structure presented above can be inserted inside "children" node. "Children" may have many configuration nodes inside and each of them may contain other configuration nodes.

The given structure of configuration makes dealing with configuration the same for all components of applications. Only the configuration of the component and its children is passed to the component and the only thing the component has to do is to read its configuration data from "Settings" node or to pass configurations from children node to its child-components.

SConfigManager Class

The configuration manager class is self efficient to work with configuration. It is initialized with constructor that has one parameter: configuration in XML string format. After proper initialization, all data is accessible by Name, Settings, CustomXml and Children properties.

Image 1

Due to the fact that XML configuration usually is stored in a text file, the ReadConfigFromFile static method is provided. It returns content of the file in the string format. This can be passed directly to the ConfigurationManager constructor.

Finally, ToString method returns the original XML that was used to create an instance of the class.

Below is the code of SConfigManager (Link to full code with comments and demo in the link is at the bottom of tip).

C#
using System;
using System.Collections.Generic;
using System.Xml;

namespace SimpleConfigurationManager
{
    /// <summary>
    /// Configuration manager class. 
    /// </summary>
    public class SConfigManager
    {
        public string Name { get; internal set; }

        public Dictionary<string, string> Settings { get; set; }

        public string CustomXml { get; set; }

        public Dictionary<string, SConfigManager> Children { get; set; }

        private string _configXml;

        public SConfigManager(string configuration)
        {
            this._configXml = configuration;
            XmlDocument configXml = null;

            // Initializing XmlDocument
            try
            {
                configXml = new XmlDocument();
                configXml.LoadXml(configuration);
            }
            catch (Exception e)
            {
                Exception exception = new Exception("Cannot parse XML file", e);
                throw exception;
            }

            // Read Name of the configuration section
            try
            {
                Name = configXml.DocumentElement.Attributes["name"].Value;

                if (Name == "")
                {
                    throw new Exception();
                }
            }
            catch (Exception)
            {
                ArgumentException exception = 
                new ArgumentException("Argument cannot null nor empty", "name");
                throw exception;
            }

            try
            {
                // parse settings
                Settings = new Dictionary<string, string>();

                XmlNodeList xnList = configXml.SelectNodes("/configuration/settings//setting");

                foreach (XmlNode xn in xnList)
                {
                    string key = xn.Attributes["key"].Value;
                    string value = xn.Attributes["value"].Value;
                    Settings.Add(key, value);
                }

                // get custom xml
                xnList = configXml.SelectNodes("/configuration/custom");

                if (xnList.Count > 0)
                {
                    CustomXml = xnList[0].InnerXml.ToString();
                }

                // get children
                Children = new Dictionary<string, SConfigManager>();

                xnList = configXml.SelectNodes("/configuration/children/configuration");

                if (xnList.Count > 0)
                {
                    foreach (XmlNode xmlNode in xnList)
                    {
                        SConfigManager childConfig = new SConfigManager(xmlNode.OuterXml.ToString());

                        Children.Add(childConfig.Name, childConfig);
                    }
                }
            }
            catch (Exception ex)
            {
                Exception exception = new Exception("Error while processing XML", ex);
                throw exception;
            }
        }

        public static string ReadConfigFromFile(string path)
        {
            XmlDocument content = new XmlDocument();

            try
            {
                content.Load(path);
            }
            catch (Exception ex)
            {
                Exception exception = new Exception("Cannot load XML from file.", ex);
                throw exception;
            }

            return content.InnerXml;
        }

        public override string ToString()
        {
            return _configXml;
        }
    }
}

Example Usage

Note: The full code with demo available at https://github.com/tymanski/SimpleConfigurationManager.

To show benefits of using ConfigurationManager, let's create a simple application. Let's take an example application that watches directories and logs that actions to log files.

Image 2

The Monitor (which is a "root", or "parent") initializes watchers. Watchers will watch for Create/Rename/Delete actions in specified directories and inform parent (Monitor) about that. Monitor will log each action detected by watchers.

Now, configuration that we will use here is as follows:

XML
<configuration name="Agent01">
    <settings>
        <setting key="AgentName" value="Main" />
    </settings>
    <customxml></customxml>
    <children>
        <configuration name="Logger">
            <settings>
                <setting key="LogDirectory" value="D:/applogs/" />
                <setting key="LogFilePrefix" value="agent_" />
                <setting key="Enabled" value="true" />
            </settings>
            <customxml></customxml>
            <children></children>
        </configuration>
        <configuration name="Watchers">
            <children>
                <configuration name="Watcher01">
                    <settings>
                        <setting key="Name" value="Watcher01" />
                        <setting key="Directory" value="D:\watcher\w1" />
                        <setting key="Filter" value="*.*" />
                    </settings>
                    <customxml></customxml>
                    <children></children>
                </configuration>
                <configuration name="Watcher02">
                    <settings>
                        <setting key="Name" value="Watcher02" />
                        <setting key="Directory" value="D:\watcher\w2" />
                        <setting key="Filter" value="*.*" />
                    </settings>
                    <customxml></customxml>
                    <children></children>
                </configuration>
            </children>
        </configuration>
    </children>
</configuration>

As you can see, the node is a configuration with name "Agent01". It has some settings and 2 nodes in Children:

  • node named "Logger"
  • node named "Watchers"

"Logger" node is configuration that will be passed to the logger (in the form of configuration parsed to ConfigManager).

"Watcher" node uses the same structure, but in this situation it is used as an array of configuration nodes. Each child of "Watchers" node is another configuration of particular Watcher.

To make a long story short: Monitor application will initialize itself, then it will initialize Logger using "Logger" configuration and then will iterate through children of its child "Watchers" and will initialize watcher for each of them.

Note that Logger and Watchers are not aware of parent configuration, they are not bothered with that. All they get is the configuration that those components need and their subcomponents (if any). From their point of view, they just expect the parent to prepare an instance of SConfigManager class and pass it to them.

Here is an example in the code how SConfigManager may be used:

C#
string configurationXml = SConfigManager.ReadConfigFromFile("D:/myapp/configuration.xml");

// Initialize SConfigManager using configuration string.
SConfigManager configuration = new SConfigManager(configurationXml);

// Get configuration name:
Console.WriteLine(configuration.Name);

// Get some settings from configuration
string username = configuration.Settings["username"];
int articlesCount = Convert.ToInt32(configuration.Settings["articleCount"]);

// Get subconfiguration
SConfigManager loggerConfiguration = configuration.Children["Logger"];
Logger logger = new Logger(loggerConfiguration);

To have a better feel of how it works, run the demo project available on github.
https://github.com/tymanski/SimpleConfigurationManager

I would be grateful for any feedback.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Unknown
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhy not XmlSerializer? Pin
Daniel Leykauf15-Oct-15 10:16
Daniel Leykauf15-Oct-15 10:16 
Hi,
In small projects i usually use the XmlSerializer to store a custom settings class. It is very handy and provides simple methods to customize the output.
More Information: https://msdn.microsoft.com/en-us/library/182eeyhh(v=vs.110).aspx[^]
Regards,
D.
AnswerRe: Why not XmlSerializer? Pin
tymanski17-Oct-15 21:20
tymanski17-Oct-15 21:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.