Click here to Skip to main content
15,879,474 members
Articles / Programming Languages / XML
Article

Simple XML Config File Reader/Writer

Rate me:
Please Sign up or sign in to vote.
2.25/5 (5 votes)
22 Aug 2008CPOL3 min read 47.1K   594   23   8
A very simple to use configuration reader/writer for .NET.

Background

Quite often, I have seen developers implementing their own version of XML configuration reader/writer which may take time to develop and maintain. Though .NET 2.0 provides a Configuration library, I have not yet seen it much in use.

Configuration is one aspect no one has a definite and consistent approach about. From my experience, I have seen most developers implementing their own version of code to store/access configuration (which could either be in database, XML files, INI files, or Registry).

In this sample, I have come up with a very basic XML configuration file, the entire sample application is based on, and I believe will be very easy to use. You can always extend the configuration file/sample application to suit your needs, or use database instead of XML etc., but I suppose this example should be good enough to get you started, and will help you maintain the code to access configuration files a lot easier.

The Simple Idea

Well, this is not a path breaking sample application, but like I mentioned, it might help developers to understand and reuse components. Also, since this application uses Reflection, custom attributes, and LINQ, I guess it will be a good sample exercise to understand the concepts at a beginner level.

So, before we begin, here is how the sample config file looks like:

XML
<Configuration> 
  <Module Name="Application-A">
    <Section Name="Section-A">
      <Entry Name="RefreshRate">11</Entry>
    </Section>
  </Module>
  <Module Name="Application-B">
    <Section Name="Section-B">
      <Entry Name="LogFile">SomeLogFile.Log</Entry>
    </Section>
  </Module>
</Configuration>

The configuration is stored inside a Module and a Section as an Entry tag.

I'll soon describe how to load and save files, but before that, let's discuss how the config variables are retrieved and saved.

The XML tags, i.e. Module, Section, and Entry, are mapped to enum values, so they are easy to read and write, analogous to property fields. This is made possible because of the custom attribute that we attach to each enum value. The custom attribute takes the respective name of the application, Section tag, and Entry tag to which it relates to; for example, the RefreshRate enum value in the example below.

C#
public enum Confiugration
{
    [CustomConfig(ApplicationName = "Application-A", 
     SectionName = "Section-A", 
     EntryName = "RefreshRate", 
     DefaultValue = "5")]
    RefreshRate,
    [CustomConfig(ApplicationName = "Application-B", 
     SectionName = "Section-B", 
     EntryName = "LogFile")]
    LogFile
};

This allows us to read the configuration using this code:

C#
configHelper.GetConfigEntry(Configuration.RefreshRate);

Similarly, you can save the configuration value with the following code:

C#
configHelper.SetConfigEntry(Confiugration.RefreshRate, "11");

Here are the five lines of custom attribute code required:

C#
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
public sealed class CustomConfigAttribute : Attribute
{
    public String ApplicationName { get; set; }
    public String SectionName { get; set; }
    public String EntryName { get; set; }
    public String DefaultValue { get; set; }
}

In short, you didn't have to implement any code to read or write the variable; all you have to do is create an enum for your configuration, and fill in the appropriate attribute values attached to the enum value, and let the CustomConfigHelper class read and write the configuration entry for you.

I am sure it must be obvious to you that the CustomConfigHelper class provides functions that reads the attributes attached to the enum values and, as required, reads/updates the config file.

To read the custom attributes attached to an enum, the CustomConfigHelper class uses Reflection on the enumeration that you pass. For example, for the GetConfigEntry function, we'd do something like this:

C#
public String GetConfigEntry(Enum enumValue)
{  
    Type type = enumValue.GetType();
    FieldInfo info = type.GetField(enumValue.ToString());
    var customAttribute = Attribute.GetCustomAttribute(info, 
        typeof(CustomConfigAttribute)) as CustomConfigAttribute;

Once the custom attribute is retrieved we can then use LINQ to retrieve the appropriate entry from the xml file. To load the xml file we use XElement.Load function

C#
if (File.Exists(fileName))
{
    rootNode = XElement.Load(fileName); 
    var item = from applicationNode in rootNode.Elements("Module")
               where (String)applicationNode.Attribute("Name") == 
                      customAttribute.ApplicationName
               from sectionNode in applicationNode.Elements("Section")
               where (String)sectionNode.Attribute("Name") == 
                      customAttribute.SectionName
               from entryNode in sectionNode.Elements("Entry")
               where (String)entryNode.Attribute("Name") == 
                      customAttribute.EntryName
               select entryNode.FirstNode;

    if (item.Any())
    {
        return item.First().ToString();
    }
}

Similarly, we can also save the configuration:

C#
var item = from applicationNode in rootNode.Elements("Module")
                       where (String) applicationNode.Attribute("Name") == 
                              customAttribute.ApplicationName
                       from sectionNode in applicationNode.Elements("Section")
                       where (String) sectionNode.Attribute("Name") == 
                              customAttribute.SectionName
                       from entryNode in sectionNode.Elements("Entry")
                       where (String) entryNode.Attribute("Name") == 
                              customAttribute.EntryName
                       select entryNode;

if(!item.Any())
{
    return;
}

item.First().Value = entryValue;

Points of Interest

Here is the full code of the CustomConfigHelper class that does the magic for you:

C#
public class CustomConfigHelper
{
    private static XElement rootNode;

    private CustomConfigHelper(){}
    private static String configFileName { get; set; }

    public CustomConfigHelper(String fileName)
    {
        if (File.Exists(fileName))
        {
            rootNode = XElement.Load(fileName);
            configFileName = fileName;
        }
    }

    private static void SaveConfigFile(String fileName)
    {
        rootNode.Save(fileName);
    }

    public String GetConfigEntry(Enum enumValue)
    {
        Type type = enumValue.GetType();
        FieldInfo info = type.GetField(enumValue.ToString());
        var customAttribute = Attribute.GetCustomAttribute(info, 
            typeof(CustomConfigAttribute)) as CustomConfigAttribute;

        return customAttribute == null ? String.Empty : 
               GetConfigEntry(customAttribute);
    }

    public void SetConfigEntry(Enum enumValue, String entryValue)
    {
        if(entryValue == null)
        {
            entryValue = String.Empty;
        }

        Type type = enumValue.GetType();
        FieldInfo info = type.GetField(enumValue.ToString());
        var customAttribute = Attribute.GetCustomAttribute(info, 
            typeof(CustomConfigAttribute)) as CustomConfigAttribute;
        SetConfigEntry(customAttribute, entryValue);
    }

    private static String GetConfigEntry(CustomConfigAttribute attribute)
    {
        if (rootNode == null)
        {
            return attribute.DefaultValue ?? String.Empty;
        }

        var item = from applicationNode in rootNode.Elements("Module")
                   where (String)applicationNode.Attribute("Name") == 
                          attribute.ApplicationName
                   from sectionNode in applicationNode.Elements("Section")
                   where (String)sectionNode.Attribute("Name") == 
                          attribute.SectionName
                   from entryNode in sectionNode.Elements("Entry")
                   where (String)entryNode.Attribute("Name") == 
                          attribute.EntryName
                   select entryNode.FirstNode;

        if (item.Any())
        {
            return item.First().ToString();
        }

        return attribute.DefaultValue ?? String.Empty;
    }

    private static void SetConfigEntry(CustomConfigAttribute attribute, 
                                       String entryValue)
    {
        if (rootNode == null)
        {
            return;
        }

        var item = from applicationNode in rootNode.Elements("Module")
                   where (String) applicationNode.Attribute("Name") == 
                          attribute.ApplicationName
                   from sectionNode in applicationNode.Elements("Section")
                   where (String) sectionNode.Attribute("Name") == 
                          attribute.SectionName
                   from entryNode in sectionNode.Elements("Entry")
                   where (String) entryNode.Attribute("Name") == 
                          attribute.EntryName
                   select entryNode;

        if(!item.Any())
        {
            return;
        }

        item.First().Value = entryValue;
        SaveConfigFile(configFileName);
    }
}

I hope this helps. Comments and suggestions as always are appreciated. Cheers.

History

Original version.

License

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


Written By
Software Developer (Senior)
Singapore Singapore
I am working for an investment bank in Singapore for low latency applications utilising .net framework. I have been coding primarily on .net since 2001. I enjoy photography and trance genre.

Comments and Discussions

 
QuestionThis is old but: how to make a new file? Pin
Moxxis18-Aug-14 1:44
Moxxis18-Aug-14 1:44 
GeneralMy vote of 2 Pin
shelby6719-May-11 13:34
shelby6719-May-11 13:34 
GeneralGood, but... [modified] Pin
PIEBALDconsult22-Aug-08 10:37
mvePIEBALDconsult22-Aug-08 10:37 
GeneralRe: Good, but... Pin
Robert Kozak22-Aug-08 11:00
Robert Kozak22-Aug-08 11:00 
GeneralRe: Good, but... Pin
PIEBALDconsult22-Aug-08 11:19
mvePIEBALDconsult22-Aug-08 11:19 
GeneralRe: Good, but... Pin
Robert Kozak22-Aug-08 13:14
Robert Kozak22-Aug-08 13:14 
GeneralRe: Good, but... Pin
Robert Kozak22-Aug-08 14:24
Robert Kozak22-Aug-08 14:24 
GeneralRe: Good, but... Pin
RohitOn.Net22-Aug-08 16:37
RohitOn.Net22-Aug-08 16:37 
Sure !!! Having an an overloaded constructor with XPath would be a good idea too. Cheers.

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.