65.9K
CodeProject is changing. Read more.
Home

User options in XML

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (9 votes)

Nov 3, 2004

2 min read

viewsIcon

51815

downloadIcon

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:

  1. Lightweight - This should be simple and should not require much code. The more code we write, the more faults we introduce into an application!
  2. Extensible - It should be very simple to add new options. As the application feature set grows, so will the number of user options.
  3. 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();
         }
      }
   }
}