Click here to Skip to main content
15,867,141 members
Articles / Programming Languages / C#

XML Configuration Files Made Simple... At Last!

Rate me:
Please Sign up or sign in to vote.
4.45/5 (37 votes)
14 Jan 20077 min read 222K   5.5K   104   38
A simple to use wrapper class for reading and writing XML configuration files

Introduction

Well first of all, I think I should start with an introduction. My name is Vandra Akos, and I'm a 12th grade student at High School. I have been programming since I was a kid, well... more or less "programming". I'm only 18 years old, and this is my first article ever, so please help me out if I'm not doing something right, leave a note in the comments section below if you need clarification on what's a little confusing. Thanks!

In the past, I used Delphi's TIniFile and TMemIniFiles a lot to store my configurations. I really, really liked the way they were designed, easy to use, but they had a small problem: no possibility to nest settings; only sections were defined to group similar settings together, but sometimes that is just not enough.

Usually an .ini file looks like this:

[section1]
option1=value1
option2=value2
option3=value3
[section2]
option1=value1
option2=value2

Sometimes, you have to describe complex objects, and then usually you have to use ugly names like:

listbox-width = 640
listbox-item1-child-name="foo"

So when I first heard about XML, and what it was good for, I decided to make a class similar to these ones. Well, I have to say, I started making the class at least thrice, but surrendered before the mighty complexity of XML. Later, I moved to C#, and I started thinking about XMLConfig again and again. But I couldn't find enough documentation on the subject. But in the end, I gathered enough to hook up the class.

What is This Class?

It is similar to accessing .ini files, but this time, settings can be nested. You can simply read and write any value from / to it, without bothering with XML parsing. The configuration file is hierarchical (doh, it's XML!), and any node can contain a value, even those with other children. This could come in handy sometimes.

What is a Valid XML Config File?

Only two small rules apply:

  • No nodes with two children with the same name. Otherwise, which one to retrieve on xcfg.Settings["foo"]? [This is now possible since v2.0 beta].
  • Only alphanumerical names may be used (numbers and digits). That should be enough. Just not to mess with XPath by any chance, which I didn't look into thoroughly.

When loading and saving the XML files, they are automatically validated, and an exception thrown if they are not valid.

How to Construct the XMLConfig Object?

You can either load a configuration file from disk, stream, or string, or create a new one from scratch. If you load it from disk, you can specify what should happen if the file does not exist. Create the file, or throw an exception?

  • Create a file from scratch, rootname = "xml":
    C#
    XMLConfig xcfg = new XMLConfig();
  • Load a file from disk, create if non-existent:
    C#
    XMLConfig xcfg = new XMLConfig("config.xml",true);
  • Load a file from disk, throw exception if file does not exist:
    C#
    XMLConfig xcfg = new XMLConfig("config.xml",false);
  • Loads configuration from an XML string:
    C#
    XMLConfig xcfg = new XMLConfig();
    xcfg.LoadFromString(xmlstr);
  • Create a new configuration file with myapp as the root element:
    C#
    XMLConfig xcfg = new XMLConfig();
    xcfg.NewXML("myapp");

Okay, So It's Constructed... Now... How to Use It?

This is broken into three parts:

  1. Accessing values which have unique names
  2. Accessing values which are not uniquely named
  3. Removing values

Accessing Uniquely Named Values

The XMLConfig.Settings exposes the root node of the settings, and it will be used to access any setting in the file. Each node is represented by a ConfigSetting object, which contains properties to access its value, and an indexer to access its children. The indexers parameter takes a string, which will be either the child's name to be accessed, or a path, separated either by slashes '/', or backslashes '\'.

For example:

To access screen.window.width, you can either use:

  • xcfg.Settings["screen"]["window"]["width"].Value

    or:

  • xcfg.Settings["screen/window/width"].Value

The former can be handy to initialize objects by passing them the respective ConfigNode, the latter is shorter, and more comfortable to type in by hand.

Because they are all properties, their use is really simple, as the following example demonstrates:

C#
Xmlconfig xcfg = new Xmlconfig("config.xml",true);
xcfg.Settings["window"]["width"].intValue = 800;
xcfg.Settings["saveonclose"].boolValue = true;
xcfg.Settings["listbox"]["item1"]["name"].Value = "foo";

listbox1.width = xcfg.Settings["listbox/width"].IntValue;
label1.caption = xcfg.Settings["label1"]["caption"].Value;
Myclass.MySubclass.MySubSubclass.Enabled = 
   xcfg.Settings["my1/sub1/sub2/enabled"].boolValue;

Accessing Values Which Are Not Uniquely Named

This can happen when the configuration file contains a list of plug-ins, for example, or a set of display modes. I am going to guide you through using the plug-in example.

I updated the path syntax, so now between the slashes, the correct syntax is "nodename#n", where the "#n" can be omitted, and n specifies the index of the value to return. If omitted, n is 1 by default, so if multiple children exist with the same name, the first one is returned. A special case exists, when n is not an int value, but is "#", so that the path looks like "nodename##". In this case, a new, empty node is created, with the desired name. Actually, ## equals with GetNamedChildrenCount + 1.

Be careful though, accessing nodename#1000000000 will make 1 000 000 000 children, and will probably crash due to insufficient memory!

XML
<xml>
    <startminimized value="False">
  <width value="1024">
  <height value = "768">
  <dummy />
  <plugin>
    <name value="Show Warning Message Box">
    <dllfile value="foo.dll">
    <method value = "warning">
  </plugin>
  <plugin>
    <name value="Show Error Message Box">
    <dllfile value="foo.dll">
    <method value = "error">
  </plugin>
  <plugin>
    <name value="Get Random Number">
    <dllfile value="bar.dll">
        <method value = "random">
  </plugin>
</xml>

There are other methods as well to retrieve children methods with the same name. For example, you can use the ConfigSetting.GetNamedChildren(string) method to get an IList<string> object containing all nodes with the specified name, or use ConfigSetting.GetNamedChildrenCount(string) to only get the number of children with the specific name:

C#
xcfg.Settings["plugin#1/name"].Value // = Show Warning Message Box
xcfg.Settings["plugin#3/dllfile"].Value // = bar.dll

Console.Write(xcfg.Setting.GetNamedChildrenCount("plugin")) // 3
foreach(ConfigSetting cs in xcfg.Setting.GetNamedChildren("plugin"))

{
  Console.Write(cs["method"].Value+" ");

} // Outputs "warning error random"

That was about reading / modifying already existent data. But what about creating another node with the specific name? Think about that "##" I have mentioned earlier... Yep, we can use that to simply create new nodes with already existing names. So to create another plug-in, we will use the code below:

C#
ConfigSetting newplugin = xcfg.Settings["plugin##"];
// Append a new plugin node

newplugin["name"] = "Show Info Message Box";
newplugin["dllfile"] = "foo.dll";
newplugin["method"] = "information";

I find that really simple, don't you? Well, I hope you do. No need to check if a node exists, create it otherwise. Just access it like it was there. If it wasn't, it will be created automatically. Of course, this can be overridden, and an exception thrown in that case.

Removing Values

You can simply use ConfigSetting.CleanUp() to remove any empty nodes. You can also call the method on the XMLConfig object, it will automatically clean up the whole tree. You could also set the XmlConfig.CleanUpOnSave boolean property to true (false, by default), then empty nodes will automatically be stripped from the tree before saving it.

You can remove a node with all its children (not necessarily empty-valued) by calling ConfigSetting.Remove(), or you could use ConfigSetting.RemoveChildren() to remove each and every child, but keep the node itself, and its value.

C#
xcfg.Settings["plugin#1"].Remove(); 
        // Removes 
        //    <plugin>        
        //    <name value="Show Warning Message Box">
        //    <dllfile value="foo.dll">
        //    <method value = "warning">
        //  </plugin>
        // Portion from the XML file
xcfg.Settings["plugin#2"].RemoveChildren();
        // Removes 
        //    <name value="Show Error Message Box">
        //    <dllfile value="foo.dll">
        //    <method value = "error">
        // Portion from the XML file

xcfg.CleanUp();
// Removes any empty nodes from the tree,
// that is the <dummy /> from the example

Okay, Created It... Modified it... Now Let's Save It!

Erm.... You don't need to. That is, if you loaded it from disk. The config file exposes a CommitOnUnload property, and, if true, calls the Commit() function when loading another file/string or destroying the XMLConfig object. Of course, you could also use the Save(filename); method to save it to disk or Save(stream); method to save it to a stream.

Warning: The Commit() and Reload() methods work only for files loaded from disk, or configurations which were loaded from memory, but already saved with the Save(filename) method to disk!

History

  • Version 2.0 beta - Updated source on January 14, 2007
  • Version 2.0 beta - Released on January 6, 2007
    • Added support for multiple children with the same name, some minor changes in the way multiple values are returned (IList instead of arrays)
    • Some of the code is not optimized, there are pretty ugly hacks in there, if you know a simpler and nicer way to do them, please let me know.
  • Version 1.0 - Released on December 31, 2006

Additional Information

The compiled DLL contains an object named MD5 with a static hash (string) method also. It can be useful for getting MD5 sums of strings.

All objects are in the VAkos namespace.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Romania Romania
I was born in 1988, and have been working at school since I was 7 Smile | :)

Comments and Discussions

 
QuestionAdding a new Node Pin
Maurcio8-Dec-19 11:45
Maurcio8-Dec-19 11:45 
QuestionSort Method Pin
raoulFR30-Aug-14 0:48
professionalraoulFR30-Aug-14 0:48 
QuestionException Pin
Anders Eriksson14-Sep-11 2:12
Anders Eriksson14-Sep-11 2:12 
GeneralMy vote of 5 Pin
ahhbees17-Jul-11 14:42
ahhbees17-Jul-11 14:42 
QuestionLicense Pin
CKing4live29-May-11 0:34
CKing4live29-May-11 0:34 
GeneralGitHub Pin
Joan Barros14-Mar-11 9:38
Joan Barros14-Mar-11 9:38 
NewsBe accurate Pin
BMW74014-Feb-11 22:45
BMW74014-Feb-11 22:45 
GeneralWell Done! Pin
zendance8-Feb-11 10:07
zendance8-Feb-11 10:07 
GeneralProblem with special naming convention Pin
Przemek Kłys22-Jun-10 4:45
Przemek Kłys22-Jun-10 4:45 
GeneralRe: Problem with special naming convention Pin
Pelgar10-Dec-10 4:38
Pelgar10-Dec-10 4:38 
GeneralExceptions! Pin
weezer31621-Apr-10 5:00
weezer31621-Apr-10 5:00 
GeneralRe: Exceptions! Pin
Luke Hoffmann12-Aug-11 20:46
Luke Hoffmann12-Aug-11 20:46 
QuestionBut how do I install it? Pin
tiwas16-Mar-10 1:21
tiwas16-Mar-10 1:21 
GeneralVery nice, small correction Pin
Asker8021-Jan-10 22:11
Asker8021-Jan-10 22:11 
Generalvery cool Pin
Donsw8-May-09 16:06
Donsw8-May-09 16:06 
QuestionQuestion Pin
Member 42314679-Apr-09 4:22
Member 42314679-Apr-09 4:22 
Generalxml special chars Pin
Donkey Master5-Nov-07 0:03
Donkey Master5-Nov-07 0:03 
Generalsimple and nice Pin
rperetz19-Jul-07 9:30
rperetz19-Jul-07 9:30 
Generalnice work Pin
Nothingam14-Jul-07 14:13
Nothingam14-Jul-07 14:13 
GeneralRe: nice work Pin
axos8815-Jul-07 10:47
axos8815-Jul-07 10:47 
GeneralSettings["test_name"].value crashes Pin
schna00312-Jun-07 9:04
schna00312-Jun-07 9:04 
GeneralRe: Settings["test_name"].value crashes Pin
axos8815-Jul-07 10:46
axos8815-Jul-07 10:46 
GeneralNice Pin
Ben Daniel9-Jan-07 11:48
Ben Daniel9-Jan-07 11:48 
NewsVersion 2.0 beta released :) Pin
axos886-Jan-07 7:33
axos886-Jan-07 7:33 
GeneralCheck out Nini Pin
Simone Busoli3-Jan-07 3:17
Simone Busoli3-Jan-07 3:17 

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.