User options in XML






4.25/5 (9 votes)
Nov 3, 2004
2 min read

51815

171
Serialization of user options.
Introduction
Most applications need to maintain options specific to the user. In this article, I develop a single class, UserOptions
, to maintain and persist user options. The options are stored in an XML file in a sub-directory of the ApplicationData
special folder.
The ApplicationData
folder serves as a common repository for application-specific data for the current roaming user. A roaming user works on more than one computer on a network. A roaming user's profile is kept on a server on the network and is loaded onto a system when the user logs on.
The Design
While designing the UserOptions
class, I kept the following concepts in mind:
- Lightweight - This should be simple and should not require much code. The more code we write, the more faults we introduce into an application!
- Extensible - It should be very simple to add new options. As the application feature set grows, so will the number of user options.
- Polymorphic - User options can be of any type (scalar, array, string, even another object).
The above three concepts scream "OO". So, why not use a class whose properties are the options to maintain? So, we can do something like this to store where the application should start on the desktop:
public class UserOptions
{
private Point startLocation;
public Point StartLocation
{
get {return startLocation;}
set {startLocation = value;}
}
}
Serialization
We can use the XmlSerializer
class to save and load the UserOptions
.
Loading the options presents a problem in that the XmlSerializer
always returns a new object when it deserializes. My solution is to require developers to use the static
property Current
to access the options, as in UserOptions.Current.StartLocation
.
The Current
property can then determine if an instance of UserOptions
needs to be created. This solution also allows code anywhere in the application to access the UserOptions
without the need of the application maintaining a global variable.
public static UserOptions Current
{
get
{
if (current == null)
{
lock (typeof(UserOptions))
{
if (current == null)
{
current = Load();
}
}
}
return current;
}
}
Note that I lock
the type to prevent any race conditions in a multi-threaded application.
And now for the Load()
and Save()
methods:
private static UserOptions Load()
{
if (!File.Exists(OptionsPath))
return new UserOptions();
XmlSerializer serializer = new XmlSerializer(typeof(UserOptions));
using (FileStream stream = new FileStream(OptionsPath, FileMode.Open))
{
XmlReader reader = new XmlTextReader(stream);
return (UserOptions) serializer.Deserialize(reader);
}
}
public void Save()
{
XmlSerializer serializer = new XmlSerializer(this.GetType());
using (StreamWriter writer = new StreamWriter(OptionsPath))
{
serializer.Serialize(writer, this);
}
}
Sample Usage
The following snippet is from a Form
that wants to remember its window location on desktop. When it is closed, the current location is saved, and when it is created, the saved location is used.
public class MyForm : Form
{
public MyForm()
{
// Do standard stuff
// Check our options
if (!UserOptions.Current.StartLocation.IsEmpty)
{
this.StartPosition = FormStartPosition.Manual;
this.Location = UserOptions.Current.StartLocation;
}
}
protected override void OnClosed(EventArgs e)
{
UserOptions.Current.StartLocation = this.Location;
UserOptions.Current.Save();
base.OnClosed (e);
}
}
UserOptions Source
The following is a comment stripped version of the UserOptions
class. See the download for all the gory details.
using System;
using System.Drawing;
using System.IO;
using System.Globalization;
using System.Security;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
namespace BlackHen.Samples
{
public class UserOptions
{
private Point startLocation;
private static UserOptions current;
public UserOptions()
{
startLocation = Point.Empty;
}
public static UserOptions Current
{
get
{
if (current == null)
{
lock (typeof(UserOptions))
{
if (current == null)
{
current = Load();
}
}
}
return current;
}
}
private static UserOptions Load()
{
if (!File.Exists(OptionsPath))
return new UserOptions();
XmlSerializer serializer = new XmlSerializer(typeof(UserOptions));
using (FileStream stream = new FileStream(OptionsPath, FileMode.Open))
{
XmlReader reader = new XmlTextReader(stream);
return (UserOptions) serializer.Deserialize(reader);
}
}
public void Save()
{
XmlSerializer serializer = new XmlSerializer(this.GetType());
using (StreamWriter writer = new StreamWriter(OptionsPath))
{
serializer.Serialize(writer, this);
}
}
private static string OptionsPath
{
get
{
// Build the directory.
StringBuilder path = new StringBuilder();
path.Append
(
Environment.GetFolderPath
(
Environment.SpecialFolder.ApplicationData
)
);
path.Append(Path.DirectorySeparatorChar);
path.Append(Application.CompanyName);
path.Append(Path.DirectorySeparatorChar);
path.Append(Application.ProductName);
lock (typeof(UserOptions))
{
string dir = path.ToString();
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
// Add the file name.
path.Append(Path.DirectorySeparatorChar);
path.Append(Path.GetFileName(Application.ExecutablePath));
path.Append(@".options.xml");
return path.ToString();
}
}
}
}