New Twists on the Ancient Art of Persisting Application Data
(Note: If you have been keeping up with this blog it is probably apparent that the subject matter on this blog ranges widely. We have posts on topics as diverse as debugging .Net applications, web farm administration, Log Parser tips and tricks, upper management messages and today, some coding tips. There is a method in our apparent madness. All of the posts on this blog have a common thread…they are all from subteams inside Microsoft.com Operations. If you read this blog regularly you will begin to get a picture of how diverse this team is in terms of skill sets, but also how we utilize these skill sets to run Microsoft.com. Today's post comes from our Tools Team, the internal group of developers that create some of the tools we use help us do our job more effectively.)
Programs that Remember
Let’s talk about some .NET C# programming. Probably since the dawn of computer programming, developers have been inventing ways to store bits of data to make their programs “remember” or persist information between executions. Giving a program some capability to recall things makes it intelligent and convenient to use.
The bad news is that persisting application data has not always been simple to accomplish. The good news is that it has become easier as programming languages and platforms have evolved. The purpose of this article is to discuss how the .NET Framework has made this common task much easier to get done.
Persisting Application Data
One useful example of persisted data would simply be the date and time for the last time the program ran. Each time the program runs it could know how long it has been since the last time, which may drive some business rules. A more involved case would be a user interface that conveniently recalls previously-entered values, saving typing on each use.
To persist data between executions and system restarts, a program needs to store it on some disk or other drive. The data could take the form of anything from plain text to data base data. The complexity of the data format and location will determine how quickly and easily the data can be stored and retrieved by the program.
With a plain text file, the data can take any form the developer chooses. If only a single piece of data needs to be stored, the program could write the data value into a text file dedicated for holding this one value. This would make it fairly simple for the program store and retrieve the data.
But if multiple values need to be persisted, the task gets trickier. How will each value be separated/delimited? Commas? Tabs? Line feeds? How will the delimited values be identified from each other? Based on position in the list? Some kind of tag or name placed next to each value? The code that makes sense of all of this could get complex.
Wait, How Hard Could It Really Be?
Ok, maybe I am making this all seem more complicated than it needs to be. For decades, the Windows operating system has provided handy support for saving this kind of data in two useful places: the Windows registry, and INI settings files. The good old Win32 API, still accessible to .NET developers today, provides many functions designed for reading and writing values to both the registry and INI files. Visual Basic eventually added simple wrapper functions for reading and writing registry values, for example GetSetting and SaveSetting. This was a big advancement.
The Win32 API and VB functions helped a lot. I felt fortunate every time I coded a Win32 API function call. I was grateful to the Windows designers for providing relatively easy access to this large collection of hardcore system functions, making my job easier. But coding Win32 API calls is pretty “messy” by today’s standards. They are inconsistently named and used -- one big flat list of functions lacking the symmetry and organization that makes modern API’s, such as the .NET Framework, easier to use. The VB features are nice, but VB is not the best option for every application, and it provides no help for working with INI files.
INI files are nice because they can reside in any chosen folder and are easy to read with Notepad. One can easily add and remove INI data using Notepad, within these little portable structured data files.
The Windows registry is nice because it is a central repository for zillions of application data values, always found in the same place. However, your few application values can get a little lost in its vast expanses, and you still want to fire up the Registry Editor to work with it interactively – yes, you still run “regedt32”, in case you did not know.
.NET to the Rescue!
The .NET Framework now makes persisting application data a snap. First, we have two handy classes for using the Windows registry: Microsoft.Win32.Registry and Application.UserAppDataRegistry. Here is how easy UserAppDataRegistry is to use:
Application.UserAppDataRegistry.SetValue(ValueName, ValueData);
Application.UserAppDataRegistry.GetValue(ValueName);
The INI story has not improved much, though. Win32 API calls are still required, which are pretty clunky to call from within managed C# code.
But now we have better options: the glorious XML file, and a special purpose XML file called a .NET Managed Resources File or RESX file.
Regular XML files are fantastic. Application data files are just one of the countless uses for the XML file format. This self-describing, highly-structured markup language has everything that a data file needs: element names, hierarchal categorization, order, and more, all in a portable text file that Notepad can work with. Better yet, the .NET Framework provides extensive support for working with XML. This support helps make the code required to handle XML quite straightforward. For example, persisting a single data value with XML:
public string GetData_Xml(string ValueName)
{
string ReturnValue = string.Empty;
if (File.Exists(this.XmlFilePath))
{
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.Load(this.XmlFilePath);
ReturnValue = XmlDoc.FirstChild.InnerText;
}
return ReturnValue;
}
public void SaveData_Xml(string ValueName, string ValueData)
{
XmlDocument XmlDoc = new XmlDocument();
XmlDoc.LoadXml("<" + ValueName + ">" + ValueData + "</" + ValueName + ">");
XmlDoc.Save(this.XmlFilePath);
}
Have You Discovered RESX Files Yet?
However, RESX files are now my favorite choice for persisting application data. The .NET Framework support is good. You can edit them with Notepad, but not wise if you have Visual Studio handy. The XML behind RESX files is a little involved to read in a plain text editor. They are much better read and edited with Visual Studio because it presents a RESX file with a very intuitive grid editor. This editor is similar to what you might use to work with a SQL Server table in Visual Studio or SQL Server Management Studio. It is very easy to add, remove, reorder, and edit data rows.
I get the feeling that not many other application developers are using RESX files for any purpose. Before I got to know them well, about three years ago, I confused them with C resources files and imagined they would be complicated to work with. Not true! Like standard XML files, RESX files have many uses. And they sure make great vehicles for storing application data.
Following is a code snippet showing sample reading and writing to a RESX file for a single value:
public void SaveData_Resx(string ValueName, string ValueData)
{
ResXResourceWriter rw = new ResXResourceWriter(ResxFilePath);
rw.AddResource(ValueName, ValueData);
rw.Close();
}
public string GetData_Resx(string ValueName)
{
if (File.Exists(this.ResxFilePath))
{
ResXResourceReader rdr = new ResXResourceReader(ResxFilePath);
foreach (DictionaryEntry resxItem in rdr) if (resxItem.Key.ToString() == ValueName) return resxItem.Value.ToString();
rdr.Close();
}
return string.Empty;
}
Looking at the code above, you may notice that the code to save data is pretty clean, but the code to get data is a little messy. This is because you need to iterate through the entire list of data waiting to come across the chosen one. It would be much cleaner if any element could be directly accessed by its key.
Mashup a StringDictionary and a RESX File
However, it is likely that you would want to store multiple data elements. In this case you might load up all of the data from the RESX file into some intermediate object, where the program can modify the data, then save it back out to the file on disk before the program terminates. Before you think this intermediate object needs to be some custom fancy thing, I will suggest that the ideal class for this purpose is the built-in .NET System.Collections.Specialized.StringDictionary or a System.Collections.Generic.Dictionary class, typed for storing strings.
Now we are getting somewhere! We could subclass the Dictionary into a new custom class I’ll call “DataDictionary”. In the DataDictionary class we will add some code to load it up with data from a RESX file, in the constructor. Then we will give it method to save the data back out to disk when done updating the data. Compiled up into a DLL, we will be able to use this new class from any other program by adding a project reference to it. Our DataDictionary will work just like a StringDictionary, but with the data loading and saving to disk added. This will be slick since the StringDictionary is so simple to work with.
The entire DataDictionary class could be written as simply as:
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
using System.Windows.Forms;
namespace DataSaver
{
public class DataDictionary : Dictionary<string,string>
{
internal string CallingProgramName;
internal string DataFilePath;
public DataDictionary() : base()
{
CallingProgramName = Path.GetFileNameWithoutExtension(Application.ExecutablePath);
DataFilePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + Path.DirectorySeparatorChar + CallingProgramName + ".resx";
//load from disk into Dictionary
this.Clear();
if (File.Exists(this.DataFilePath))
{
ResXResourceReader rdr = new ResXResourceReader(this.DataFilePath);
IDictionaryEnumerator id = rdr.GetEnumerator();
foreach (DictionaryEntry resxItem in rdr) this.Add(resxItem.Key.ToString(), resxItem.Value.ToString());
rdr.Close();
}
}
public void Save()
{
//save to disk from Dictionary
ResXResourceWriter rw = new ResXResourceWriter(this.DataFilePath);
foreach (KeyValuePair<string, string> DataItem in this) rw.AddResource(DataItem.Key, DataItem.Value);
rw.Close();
}
}
}
And using this class could be very clean:
DataSaver.DataDictionary dd = new DataSaver.DataDictionary();
DateTime CurrentTime = DateTime.Now;
DateTime PreviousTime = DateTime.MinValue;
if (dd.ContainsKey("CurrentTime")) PreviousTime = DateTime.Parse(dd["CurrentTime"]);
string ElapsedTimeMessage = String.Format("The time elapsed since the last run was {0} seconds.", Convert.ToString(CurrentTime.Subtract(PreviousTime).Seconds));
Console.WriteLine(ElapsedTimeMessage);
dd["CurrentTime"] = CurrentTime.ToString();
dd["PreviousTime"] = PreviousTime.ToString();
dd["ElapsedTimeMessage"] = ElapsedTimeMessage.ToString();
dd.Save();
The most powerful part of that code is the way each data value is very directly accessed, such as:
dd["CurrentTime"]
No methods for getting or setting -- just simple collection array indexing. This shows the power of leveraging the .NET string Dictionary class, making a slight variant of it for our purposes.
There you have it -- new age application data persistence. Your applications now can be readily given total recall abilities, making them super intelligent, making you seem almost as intelligent.
Comments
Anonymous
January 01, 2003
Application settings in .netfx 2.0 are more versatile and convenient.Anonymous
July 31, 2006
Sweet!!! Just what I've been looking for!!!Anonymous
January 17, 2007
Thank you for your code! I thought a ResX file could only be embedded in Assemblies, but with ResXResourceReader it's a valid alternative for persisting data.Anonymous
January 19, 2007
Thank you for the feedback. I am happy to share my discoveries. =Eric