Storing Application Options and User Preferences in .NET
INI files and the registry are generally things of the past for .NET applications. But what to use instead? XML seems appropiate, but one look at System.XML is enough to scare most developers off, especially if you just want to store a few fields. Fortunately there is a very easy way in .NET to solve this, but one that is usually not seen by developers. In this article I'll explain how you can easily store and retrieve your application settings with just a few lines of code.
Why not INI, Registry or XML?
Many users have simply resorted to using INI files or the registry, because they are familiar. These methods however are bad choices in .NET.
INI Files
In Windows 3.1 developers used INI files to store settings. In general they worked reasonably well for simple settings, but were not appropriate for more complex data, or even typed data. INI files do not account for multiple users. As the size of an INI file grows, it's processing becomes quite inefficient.
Because of the simplicity, and certain drawbacks of the registry, INI files continued to be used in Win32 environments as well. INI files are not supported in .NET. To use INI files a developer must call the Win32 API directly, or use some third party classes that use the Win32 API.
Many users have simply resorted to using INI files or the registry, because they are familiar. These methods however are bad choices in .NET.
Registry
Along came the registry with Win32. The registry was fast, hierarchical, multi user, and allowed storage of typed data. But unfortunately the registry was a central system component and was not contained as part of the application files, and thus also was not backed up by many backup procedures.
For the registry, classes are available in Microsoft.Win32. XML however is portable and can be easily edited by end users if necessary.
Application.config
In .NET each application has a .config file of the format ApplicationName.exe.config. This is an XML file that can be used to configure assembly usage, HTTP proxies, webservice information, and more. This file allows advanced users to customize attributes of the application and certain FCL classes without requiring any implementation on your part. For this purpose, application.config is very well done and in fact a very welcome feature. Application.config can also be expanded to hold custom data that can be used by the developer.
To fully extend it with new sections, is a fair amount of work. However there is a predefined section that allows simple name value pairs to be entered and easily read from. For small amounts of data, this is an ideal solution.
Application.config is read only. Some developers have found ways to parse and write into sections of Application.config. However these solutions require a significant amount of code, and Application.config is not intended to be writeable. Thus, Application.config is a good choice for storage of read only configuration options, but it is not a good choice for user preferences or dynamic settings.
System.XML
XML has been gaining popularity in recent years for good reasons. XML offers fast, hierarchical storage of typed data. But XML is so flexible that often performing simple tasks is quite an undertaking.
System.XML is a set of generic classes for handling XML. System.XML is very flexible and capable but very complex to work with for simple tasks. Fortunately there are easier ways than using System.XML and handling everything yourself.
What to use instead?
If none of the previous methods are suitable, then what is suitable? Let's think about what ultimately we would like to use, then see what we can to do meet or come close to those wishes in a reasonable manner.
Such a system should be:
- Typed
- Efficient for small as well as larger amounts of data
- Able to handle complex and nested data
- Able to handle collections of data
- Easy to use
Sounds like a lot of requirements no? As with most requirements, we try to get as close as we can to what we want, with what is possible or reasonable. Fortunately in this case, there is an easy solution that is "ready to go" and provides us with everything we want. The answer lies in ADO.NET - but don't worry. We won't need a database to perform this task. There are many supporting pieces in ADO.NET that are not tied directly to a database, and one of these pieces is what we will use to fulfill our need. The solution is ADO.NET typed datasets.
ADO.NET Typed Datasets
Typed datasets are collections of in memory data tables. These datasets are used by ADO.NET to collect and transport data to and from client applications, and used by client applications to bind to GUI controls. Because the datasets are designed to be transportable for use in remoting and web services, they datasets are serializable as a whole. But for better integration with other platforms, datasets serialize to XML instead of the more raw format that most objects use.
Typed datasets are used by ADO.NET to collect and transport data to and from client applications, and used by client applications to bind to GUI controls
Because datasets are typically only used for returning data to or from a database, and the serialization is seen as a side-effect of transport, developers often overlook their other uses. Because the datasets can be typed, and can easily persist and restore from XML, they are perfect for our needs of storing and using application preferences. In fact they can be used to store limited amounts of data in a "miniature local database" as well.
Simple Demo
This demo is presented in C# and Visual Basic.NET. No Delphi for .NET demo exists as Delphi only partially supports typed datasets.
To add a typed dataset to your application, right click on the project in the solution explorer, and select Add New Item. Now select Dataset, and give the dataset a name.

Now we have a blank dataset in our project. For the purposes of a demo I have created a main form already that looks like this:

I have chosen these for the demo as they give us three types of data to store: a string, an integer, and a Boolean. Now let's design our dataset around these.
Open the solution explorer and find the newly created dataset.

When you double click on Settings.xsd, you will see a designer like this:

This is a blank dataset. A dataset can contain several tables, but for this demo we will add just one. Open the toolbox and you will see different items than you normally see in a WinForms or a WebForms application.

There are a lot of items in the toolbox related to datasets, but for the needs of this article we only need Element. Double click on Element to add one to the dataset. The dataset should now look like this:

The Element type corresponds to a datatable. Let's name it Main (change the highlighted text above to Main). Now let's enter the fields that we want to store. The element should look like this when finished:

For each element it’s a good practice to enter a value for the NullValue property. This helps if a user edits and removes items to your XML file, or you add new items and load older versions of your XML file. The NullValue property can be accessed by using the property inspector while focused on the element.
Visual studio will now take this Dataset and make a set of classes for us that we can use. So now let's take a look at the load and save buttons on the main form. These events make use of ConfigPathname; this is a field that is predefined in the demo. ConfigPathname just hold the path and filename of the settings file.
C#:
private void butnSave_Click(object sender,
System.EventArgs e)
{
Settings xDS = new Settings();
Settings.MainRow xRow = xDS.Main.NewMainRow();
xRow.Username = textUsername.Text.Trim();
xRow.PIN = int.Parse(textPIN.Text.Trim());
xRow.Admin = chckAdmin.Checked;
xDS.Main.AddMainRow(xRow);
xDS.WriteXml(ConfigPathname,
System.Data.XmlWriteMode.IgnoreSchema);
}
private void butnLoad_Click(object sender,
System.EventArgs e)
{
if (new FileInfo(ConfigPathname).Exists) {
Settings xDS = new Settings();
xDS.ReadXml(ConfigPathname,
System.Data.XmlReadMode.IgnoreSchema);
if (xDS.Main.Rows.Count > 0) {
Settings.MainRow xRow = xDS.Main[0];
textUsername.Text = xRow.Username;
textPIN.Text = xRow.PIN.ToString();
chckAdmin.Checked = xRow.Admin;
}
}
}
Visual Basic:
Private Sub butnSave_Click(
ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles butnSave.Click
Dim xDS As New Settings
Dim xRow As Settings.MainRow
xRow = xDS.Main.NewMainRow
xRow.Username = textUsername.Text.Trim()
xRow.PIN = Int32.Parse(textPIN.Text.Trim())
xRow.Admin = chckAdmin.Checked
xDS.Main.AddMainRow(xRow)
xDS.WriteXml(ConfigPathname,
System.Data.XmlWriteMode.IgnoreSchema)
End Sub
Private Sub butnLoad_Click(
ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles butnLoad.Click
If New FileInfo(ConfigPathname).Exists Then
Dim xDS As New Settings
Dim xRow As Settings.MainRow
xDS.ReadXml(ConfigPathname,
System.Data.XmlReadMode.IgnoreSchema)
If xDS.Main.Rows.Count > 0 Then
xRow = xDS.Main.Rows.Item(0)
textUsername.Text = xRow.Username
textPIN.Text = xRow.PIN.ToString()
chckAdmin.Checked = xRow.Admin
End If
End If
End Sub
If you run the demo, you can enter some test values and then click save.

After clicking Save, the demo will create a .settings file in the same directory as the .exe. Normally this is bin/debug/SettingsDemo.exe.Settings. If you open this file, you will see it is a standard XML file and can easily be edited.
Kudzu
1234
true
Now if you run the application again, you can click the Load button to load these settings.
In this demo we only stored one row in the datatable. But datatables can contain multiple rows, and a dataset can even contain multiple related or unrelated datatables.
Conclusion
XML files are a widespread standard that allows easy storage of structured typed data. Use of XML files allows easy editing by end users, and even by other software. Using typed datasets you can easily store your settings in XML files.