Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Read/Write XML files, Config files, INI files, or the Registry

0.00/5 (No votes)
20 Feb 2005 5  
A class library for reading/writing XML files, config files, INI files, or the Registry using one simple interface.

Contents

Introduction

I was developing a small Windows app using C#, and I decided that it would be nice to save the window's position and size when the app exited so it would be the same the next time the app was run. Having come from the MFC world, I searched for something equivalent to GetProfile and WriteProfile methods of the CWinApp class, but found nothing. I knew there was a Registry class in the Microsoft.Win32 namespace, but there was nothing available for writing to INI files. I would need to use the Win32 APIs for those. If I wanted to use an XML file, there was a whole slew of classes available in the System.Xml namespace. I also looked into using App.config files which some people had mentioned in the forums, but those supposedly were not meant to be written to...

All these similar mechanisms, each with their own interfaces... which one to use? I just needed a simple way to persist my window's position and size somewhere to be later retrieved.

I set my small Windows app project on the side and decided that it was time to write a new class library; one that would unify all these mechanisms into a common and simple interface: the IProfile interface. This article presents the IProfile interface and the four classes that implement it: Xml, Registry, Ini, and Config. These four classes allow reading and writing values to their respective mediums, via a common and simple interface. Enjoy!

Interface

So what does this common interface look like? Here's a simplified version of it -- see IProfile.cs or AMS.Profile.chm for the complete definition:

interface IProfile
{
  void SetValue(string section, string entry, object value);

  object GetValue(string section, string entry);
  string GetValue(string section, string entry, string defaultValue);
  int    GetValue(string section, string entry, int defaultValue);
  double GetValue(string section, string entry, double defaultValue);
  bool   GetValue(string section, string entry, bool defaultValue);

  string[] GetEntryNames(string section);
  string[] GetSectionNames ();

  void RemoveEntry(string section, string entry);
  void RemoveSection(string section);

  bool ReadOnly
  {
    get; 
    set;
  }    
  
  event ProfileChangingHandler Changing;
  event ProfileChangedHandler Changed;        
}

As you can see, it's a simple interface bearing a slight resemblance to the GetProfile and WriteProfile methods of the CWinApp class. It's all based on a simple paradigm: a piece of data is associated with an entry inside of a section. The section may contain multiple entries and there may be multiple sections available. That's it! A section could be something like "Main Window", holding entries such as "X", "Y", "Width", "Height", each one having their corresponding numeric values. If you've worked with INI files in the past, this concept will be very familiar to you.

Aside from the standard Get and Set methods used to read and write the values, the interface also allows you to retrieve the names of the available entries and sections, and to remove them if desired. It even has a ReadOnly property to prevent any changes to the profile. And to top it off, it contains two events that allow you to be notified when something in the profile (section, entry, or value) is about to change or has already changed. (I may have gone a little overboard :-) )

Classes

When it came time to implement the interface, I went with the most popular mediums available. XML was number one on the list. Its popularity is just impossible to ignore. The Registry was my second choice. Although people may be turning away from it, it's still a very efficient way to read and write data to a centralized location. Lastly, I went with INI files. I realize they're practically extinct these days but some people still use them due to their simplicity. Once I had completed my classes, I decided to add one more implementation to the list: one to handle config files. From the forums and articles here at CP, I noticed several developers needing a way to write to them. So I said, "Why not?". It's somewhat similar to the XML one so it didn't take long to write.

The result was four classes, all part of the AMS.Profile namespace: Xml, Registry, Ini, and Config. Their main objective is the implementation of IProfile based on their respective storage mediums.

So how do these classes store their Profile data? Here's a brief synopsis of how each class works:

Xml

As you probably know, XML is all about storing data inside a text file in pretty much any markup-based format. So which format would I choose to organize the data using the section/entry paradigm? After considering a couple of possibilities, I decided that the format below would be preferable, since it allows section and entry names to contain spaces. It also looks cleaner and more consistent than if I had used the section and entry names themselves to name the elements.

<?xml version="1.0" encoding="utf-8"?>
<profile>
  <section name="A Section">
    <entry name="An Entry">Some Value</entry>
    <entry name="Another Entry">Another Value</entry>
  </section>
  <section name="Another Section">
    <entry name="This is cool">True</entry>
  </section>
</profile>

Notice, the root element is called "profile". This is the default root name, which you may change via the class' RootName property. When you check out the class, you'll also notice that the default name of the XML file is based on the type and name of the application -- program.exe.xml or web.xml. This, of course, is also customizable.

Registry

For the registry, I decided to default to the old familiar path, HKEY_CURRENT_USER\Software\Company\Product, with individual sections appearing as subkeys of that. Again, this path is customizable when creating the object, or later via the RootKey and Name properties.

The above data would appear similar to this, when viewed on the Registry editor (regedit.exe):

- My Computer
  ...
  - HKEY_CURRENT_USER
    ...
    - Software
      ...
      - AMS
        - ProfileDemo
          - A Section          Name            Data
                               An Entry        Some Value
                               Another Entry   Another Value

          - Another Section    Name            Data
                               This is cool    True

Ini

INI files are pretty much self explanatory, format-wise. Here's the above data in INI format:

[A Section]
An Entry=Some Value
Another Entry=Another Value

[Another Section]
This is cool=True

Like the XML file, the default file name will be based on the name and type of the application -- program.exe.ini or web.ini. If you don't like it, it's easy enough to change via the constructor or Name property.

Config

Config files are the most complex of the bunch, format and code wise. Let me begin by illustrating how the above data would look:

<configuration>

  <configSections> 
    <sectionGroup name="profile">
      <section name="A_Section" 
          type="System.Configuration.NameValueSectionHandler, 
            System, Version=1.0.3300.0, Culture=neutral, 
            PublicKeyToken=b77a5c561934e089, Custom=null" />
      <section name="Another_Section" 
          type="System.Configuration.NameValueSectionHandler, 
            System, Version=1.0.3300.0, Culture=neutral, 
            PublicKeyToken=b77a5c561934e089, Custom=null" />
    </sectionGroup>
  </configSections>

  <profile>
    <A_Section>
      <add key="An Entry" value="Some Value" />
      <add key="Another Entry" value="Another Value" />
    </A_Section>
    <Another_Section>
      <add key="This is cool" value="True" />
    </Another_Section>
  </profile>

  <appSettings>
    <add key="App Entry" value="App Value" />
  </appSettings>

</configuration>

As you can see, there's a lot more going on here than with the other formats. The profile element contains an element for each section and the values are kept as attributes of add elements. Notice that for each section, the configSections element needs to specify how to read the values contained by it. This is all standard fare, required for the framework to properly load the values and allow you to retrieve them using the System.Configuration.ConfigurationSettings class, like this:

NameValueCollection section = (NameValueCollection)
    ConfigurationSettings.GetConfig("profile/A_Section");
string value = section["An Entry"];

Notice, there's also an "appSettings" element, which wasn't part of the other samples. I just threw it in there just to show that it may also be accessed via the Config class. If you're familiar with the System.Configuration namespace, you'll know that appSettings is the default section used for storing application-specific settings that may be read using the ConfigurationSettings.AppSettings property:

string value = ConfigurationSettings.AppSettings["App Entry"];

Well, the Config class also allows you to read (and of course, write) to that section, as follows:

Config config = new Config();
config.GroupName = null;  // don't use the "profile" group

...
string value = config.GetValue("appSettings", "App Entry", null);
config.SetValue("appSettings", "Update Date", DateTime.Today);

One thing to keep in mind for Windows apps is that .NET caches the config data as it reads it, so any subsequent updates to it on the file will not be seen by the System.Configuration classes. The Config class, however, has no such problem since the data is read from the file every time, unless buffering is active.

Like the Xml and Ini classes, the default file name will be based on the name and type of the application -- program.exe.config or web.config. This is the name expected by the .NET framework classes, but you can still change it if you want. Keep in mind that writing to web.config causes the application to end along with all the active Sessions.

Usage

So now that you've seen the classes, how do you go about using them in your code? Well, I packaged them inside their own DLL, called AMS.Profile.dll. This makes them easy to use in your various projects, without having to recompile them everywhere. Simply add the DLL to your project's References, which is done as follows inside Visual Studio .NET:

  1. Open your own project inside Visual Studio .NET.
  2. Inside the Solution Explorer toolbox, right click on the References folder and select "Add Reference".
  3. Click on the Browse button and select the AMS.Profile.dll file.
  4. Click OK. You may now use the AMS.Profile namespace inside your project.

Which class should I use?

OK, so how do you actually use these classes inside the code? That's the easy part, actually. The hard part may be deciding which one of the four to use. Whereas before you may have based your decision on the amount and complexity of the code involved, now that's no longer an issue. Now you just worry about which storage medium is best for the job. And that part is basically up to your program's requirements and/or personal preferences.

Here are some of my observations to help you decide:

  • When you bring up the Demo program, you'll see a Test button. It's used to test most of the functionality of the selected Profile to verify it's working as expected. Well, I added some simple timing code to the method to measure how long the test takes for the selected profile. The end result: the Registry profile is by far the quickest of the four (especially after the first time). The INI profile came in at a distant second, followed by XML, and then Config. (When you activate buffering on the Xml or the Config classes, their performance improves dramatically and approximates that of the Registry. However, buffering requires extra code to actually update the file.)
  • According to many people, config files are not meant to be written at run-time. The lack of .NET methods for doing so proves that. A better alternative is the Xml class which is meant to work with a separate file.
  • INI files may be old and gray, but for storage of small data, they're still hard to beat, as far as their format goes.

Still trying to decide? Here's the bottom line.

  • Use the Config class only if you absolutely need to write to the app.config file, or for your own file.
  • Use the Ini class if your program works with legacy data stored in INI files.
  • Use the Registry class if you don't want to deal with extra files, or if you require top performance.
  • Use the Xml class if you need to make it easy to transfer the whole app to another location, or to add an extra element of coolness to your app. ;-)

Even if you don't pick the proper class from the start, it's easy enough to switch to another one later. OOP rocks!

How do I use the class?

Now that you have selected the profile class for the job, how do you use them inside your code? Well, most of the time, you'll just declare the class as a field of another class and then call the GetValue and SetValue methods. Here's an example with the Xml class:

Xml profile = new Xml();
...
int width = profile.GetValue("Main Window", "Width", 800);
int height = profile.GetValue("Main Window", "Height", 600);
...
profile.SetValue("Main Window", "Width", this.Size.Width);
profile.SetValue("Main Window", "Height", this.Size.Height);

Keep in mind that the Xml and Config classes allow you to use buffering to improve the performance of reads and writes to their respective files. If you use C#, you can take advantage of the using statement to easily create the buffer, write to it, and then automatically flush it (when it's disposed). Here's an example:

Xml profile = new Xml();
...
using (profile.Buffer())
{
  profile.SetValue("Main Window", "Width", this.Size.Width);
  profile.SetValue("Main Window", "Height", this.Size.Height);
}

As you saw from the IProfile interface above, there are several other methods, properties, and events available. Those you'll find fully documented inside the code, as well as in the downloadable documentation (AMS.Profile.chm zipped). I recommend you view the help file to get the details on everything that's available from the DLL. Of course, if you have questions or concerns, you may address them to me personally or by posting a message below.

Demo

I wrote the demo program to illustrate the functionality of the four Profile classes, and at the same time, to test the classes. I added a Test button that calls a method of the Profile base class to verify that the most important methods and properties work correctly and consistently across all profiles. You may want to check out that code for examples of how to use these classes.

Keep in mind that this is just a demo program, not a utility. Each Profile object works with their default names. For example, for the Xml object, the name of the file will be Demo.exe.xml and will be located in the same folder as Demo.exe. There's no provision for changing these names, since, again, it's just a demo.

To use the demo, select a Profile from the top combo box. This will cause the sections in the profile to be placed into the Section combo. Choose a section from there and you'll see all of its entries get added to the Entry combo box. Choose an entry from there and you'll see its value placed into the Value textbox. If you want to add a new Section or Entry, simply type it into the proper combo box. Press the Save button to write the value to the profile via SetValue. This should all be pretty straightforward, I hope. If not, please let me know.

History

  • Version 1.0 - Oct 23, 2003
    • Initial release.
  • Version 1.1 - Mar 15, 2004
    • Fixed a small bug in the Config class that was causing multiple identical entries. Thanks to Russkie for reporting it and giving me the fix.
    • Added ProgramExe property to the Profile class to prevent the DefaultName from raising an exception when Assembly.GetEntryAssembly() is null.
    • Added Encoding property to the Config and Xml properties to allow changing of the default encoding (UTF8) for new files.
    • Corrected some minor documentation typos.
  • Version 2.0 - Feb 17, 2005
    • Added XmlBased class to give the Xml and Config classes a common base class for the new Buffering code.
    • Added Buffering related methods to the XmlBased classes (Config and Xml) to dramatically improve the performance of reads and writes to the underlying XML files. Thanks to all those who requested it and to brabchev for showing me his implementation.
    • Moved the Encoding property and GetXmlDocument method from the Xml and Config to their new XmlBased class.
    • Added XmlBuffer class to encapsulate the new Buffering feature added to the XmlBased classes (Xml and Config).
    • Went through every method and verified that all exceptions that may be raised are documented. Thanks to Matt Gerrans for explaining the importance of this. (BTW, I decided not to create custom exception classes, to keep the code simple and compatible for those who are currently using it.)
    • Changed the way exceptions are documented, to be consistent with everything else.
    • Added checks to where I call WritePrivateProfileString in the Ini class to raise a Win32Exception in case of failure.
    • Removed the ProgramExe (protected) property from the Profile class and replaced it with DefaultNameWithoutExtension to retrieve the value to be used by DefaultName without the profile-specific extension.
    • Changed DefaultName of the file-based classes to use the new DefaultNameWithoutExtension property and return the name based on the type of the application running (e.g., web.config for web apps). Thanks to Marcel Kunz for suggesting it.
    • Changed GetSubKey in the Registry class to open the keys more discriminately. Thanks to Kim Hellan for pointing this out (and for being patient).
    • Changed SetValue of Xml to allow writing of empty strings. Thanks to Gary Montante for pointing this out.
    • Fixed small bug in the demo project where the Save button was causing the Entry and Name boxes to be cleared.
    • Fixed another small bug in the demo project where the Test button was not accurately calculating the time taken by the test.

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