Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Simple and flexible way to access and store configuration settings

4.59/5 (11 votes)
6 Jan 2012CPOL4 min read 31.9K   299  
An easy way to manage application configuration with a wide range of options for storage.

Introduction

Recently I have been working on a task that required a lot of flexibility in storing and accessing configuration settings. I have searched the web and found plenty of solutions and examples but none of them really satisfied my requirements. This forced me to give up the search and to consider writing my own version. Once I got to the point that it appeared to be working, I decided to share it in case someone might find it more useful than the rest of the options out there.

Background

In order to finish the task, I had to make sure the following conditions were met in order to make it useful:

  1. User should be able to easily change or choose how and where configuration data is stored without affecting the configurable objects.
  2. Accessing and storing configuration should be very simple and transparent to the user but at the same time reliable.
  3. Types of any of the child objects are not known to the process that initiates loading of configuration and child objects are created depending on specific configurations.

A potential solution came to mind when I was working on a web project and used View State to store some values between page post backs. It reminded me that strings can represent any data including binary. They are relatively easy to use and can be stored pretty much anywhere - XML file, database, or even binary file. And just like we operate with strings in XML, we can access and store data as strings and convert it to appropriate data types when loading.

In order to be able to manage complex configurations, I had to come up with two concepts which we’ll call context and value. Context is simply a container that holds a collection of other contexts and values, and values are just simple string values. The loading and saving is done using two methods that perform reading and writing configuration to the appropriate context which is similar to the IXmlSerializable interface.

Using the code

You will need to implement the IConfigurationElement interface so that class configuration can be saved and loaded. The interface is similar to the IXmlSerializable interface and includes two methods: ReadConfiguration and WriteConfiguration. As their names suggest, one is used to read configuration and the other to write.

C#
public interface IConfigurationElement
{
    void ReadConfiguration(IConfigurationContext context);
    void WriteConfiguration(IConfigurationContext context);
}

Since we are dealing with strings only, we can use indexers to load and save a value. It will be the user’s responsibility to convert a string to the appropriate data type when reading, and back when writing. For example:

C#
// writing
context["DateOfBirth"] = DateOfBirth.ToShortDateString();

// reading
DateTime dt;
DateTime.Parse(context["DateOfBirth"], out dt);

Question now, what do we do if we want to save a subset of configuration values? In this case, you need to obtain a context object from the current context by ether creating a new one or getting an existing one. For this, IConfigurationContext provides three methods: Get, GetAll, and Create. The Get method accepts a string parameter that specifies the name of the parameter and returns a list of contexts. GetAll will return all contexts associated with the current context and you can access the context name by using the property Name. Accordingly, the Create method will create a new context with the provided name.

Here is the full interface definition:

C#
public interface IConfigurationContext
{
        IEnumerable<IConfigurationContext> Get(string contextName);
        IEnumerable<IConfigurationContext> GetAll();
        string this[string name] { get; set; }
        string Name { get; }
        IConfigurationContext Create(string contextName);
}

Below is an example of accessing and storing a person's name (FirstName and LastName) and postal address (MailingAddress). The example below does not include the implementation of the PostalAddress class but it is available in the attached source code together with other validation logic not shown here.

C#
public class Person : IConfigurationElement
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public PostalAddress MailingAddress { get; set; }

    public void WriteConfiguration(IConfigurationContext context)
    {
        context["FirstName"] = FirstName;
        context["LastName"] = LastName; 
        var ctx = context.CreateContext("MailingAddress");
        MailingAddress.WriteConfiguration(ctx);
    } 

    public void ReadConfiguration(IConfigurationContext context)
    { 
        FirstName = context["FirstName"];
        LastName = context["LastName"];
        var ctx = context.GetContext("MailingAddress");
        MailingAddress.ReadConfiguration(ctx);
    }
}

Now let’s look at how this can be used. For this demonstration, I created a working implementation of IConfigurationContext that uses XmlNode to store configurations. So below is an example that should be simple enough to understand.

C#
// loading
static void Main()
{
    var xmlDoc = new XmlDocument();
    xmlDoc.Load("FileName");
    var context = new XmlConfigurationContext(xmlDoc.DocumentElement);
    var person = new Person();
    person.LoadConfiguration(context);
}

// saving
static void Main()
{
    var xmlDoc = new XmlDocument();
    var context = new XmlConfigurationContext(xmlDoc, "Person");
    person.WriteConfiguration(context);
    xmlDoc.Save("FileName");
}

And below is an example of the created configuration file:

XML
<Context Name="Person">
 <Value Name="FirstName">FirstName</Value>
 <Value Name="LastName">LastName</Value>
 <Context Name="MailingAddress">
  <Value Name="Street1">Street1</Value>
  <Value Name="Street2">Street2</Value>
  <Value Name="City">City</Value>
  <Value Name="State">State</Value>
  <Value Name="Zip">Zip</Value>
 </Context>
</Context>

So let’s go back and review my requirements.

  1. User should be able to easily change or choose how and where configuration data is stored without affecting the configurable objects. We accomplished that by simply providing different implementations of IConfigurationContext. So instead of XmlConfigurationContext, we can easily create DatabaseConfigurationContext or WebServiceContext or other variations.
  2. Accessing and storing configuration should be very simple and transparent to the user but at the same time reliable. Accessing settings is straightforward and simple using the simple indexer.
  3. Types of any of the child objects are not known to the process that initiates the loading of the configuration. When loading Person, we do not know what settings it will save nor do we care, we simply provide the context where to save or find the needed values.

License

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