http://www.developer.com/

Back to article

Building Persistence into .NET Programs


June 16, 2003

If you've read my article Properly Building Persistent Programs, you know the importance of persisting selected information to enhance the user's experience of your applications. But perhaps you're still a bit shakey on the mechanics. In this article, I'll work through a simple persistence example in C#, showing how you can leverage the power of the .NET Framework to store information safely and flexibly.

The Sample Application

Figure 1 shows a simple C# application that can benefit from a bit of persistence. In this case, I want to save the form's size and position, as well as the data entered in the two textboxes. When the form is reloaded in the future, it should use the saved information to initialize itself.

A form in need of persistence

In writing the code, I need to keep several key points in mind:

  1. The application should function normally even if the persisted information can't be found. This will be the case the first time the application is run.
  2. The information should be persisted in a location that is available to all users, even if they are non-administrative or roaming users.
  3. The code should be simple and extensible, so that adding additional persisted information in the future is easy.

It All Starts With a Bit of Class

The first step in my solution is to define a class whose public fields will store the required information:

using System;
namespace Persistence2
{
	/// <summary>
	/// Class to hold persisted settings
	/// </summary>
	[Serializable()]
	public class Settings
	{
		public int Top;
		public int Left;
		public int Height;
		public int Width;
		public string Name;
		public string Email;
	}
}

By storing all of the settings in a single class, I can vastly simplify the code to read and writing settings (as you'll see in a bit). The class also makes for easy extensibility; I know that if I need to persist another bit of information, I'll just need to add one more public field to the class. At any time, I'll have at most one instance of this class in my application. In most applications where I use this pattern, I instantiate the class from persisted information when the application is launched, and create a new instance to save when the application is being shut down.

Working Smarter, Not Harder

Now it's time to consider the other requirements. I'll start with saving the settings when the application shuts down. The major problems to be dealt with here are designing a format to hold the settings, and figuring out where to put them. The format must be capable of holding all of the required information. The location must be accessible to all users: administrators, non-administrators, roaming users. Ideally the code should be the same on all supported operating systems.

At this point, the novice programmer might sit down with the MSDN Library and start slinging code, using API calls to retrieve the user's data directory, and writing low-level code to format the persisted information directly to a disk file. But the experienced developer knows that there is a better way to proceed. The .NET Framework includes hundreds of classes and thousands of members. If the application can find the necessary functionality in the Framework, the developer will have to write far less code. As a bonus, functionality that's baked into the Framework has probably had far more testing than your own code will ever see.

Sure enough, there are Framework classes that do just about everything this application needs. In particular, I'll make use of the IsolatedStorageFile class and the SoapFormatter class. Here's my code for saving settings:

using System.IO;
using System.IO.IsolatedStorage;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
	try
	{
		// Get the isolated store for this assembly
	    IsolatedStorageFile isf =
		    IsolatedStorageFile.GetUserStoreForAssembly();

		// Create or truncate the settings file
		// This will ensure that only the object we're
		// saving right now will be in the file
		IsolatedStorageFileStream isfs1 = 
		    new IsolatedStorageFileStream("HTMLBuilderSettings.xml",
		    FileMode.Create, FileAccess.Write, isf);

		// Create a settings object
		Settings s = new Settings();
		s.Top = this.Top;
		s.Left = this.Left;
		s.Height = this.Height;
		s.Width = this.Width;
		s.Name = txtName.Text;
		s.Email = txtEmail.Text;

		// Serialize the object to the file
		SoapFormatter SF = new SoapFormatter();
		SF.Serialize(isfs1, s);
		isfs1.Close();

	}
	catch (Exception ex)
	{
	// If settings can't be saved, next 
	// run will just the use form defaults
	}
}

The IsolatedStorageFile class is the .NET Framework's answer to the problem of where to store things on a user-by-user basis. Rather than force you to deal with the different available locations in different circumstances, the Framework takes care of all those messy details for you. When you instantiate an IsolatedStorageFile object, you get back an entire virtual file system that you can populate with directories and files. The user of the application is guaranteed to be able to read and write to this virtual file system. If they have a roaming profile, it will follow them around. In this particular bit of code, I've asked the .NET Framework to deliver an isolated store based on both user and assembly, so it won't conflict with stores assigned to any other user or application. After getting the store, I can create an IsolatedStorageFileStream object in it, which acts like any other Stream object.

The next step in the code is to create an instance of the Settings object and to populate it with the appropriate data from the application. Finally, I want to write the object out in a format that preserves its structure, and that can be reconsistituted to the original object. Once again, the .NET Framework comes through for me here. You're probably familiar with the Simple Object Access Protocol, SOAP, in the context of Web Services. SOAP specifies a way to encode objects into XML. That's exactly what I want to do here; fortunately, the Framework lets me call directly into the SOAP formatting engine by instantiating a SoapFormatter object and calling its Serialize method.

So, when this code runs, my Settings object will be packed up in a neat little XML file and stored somewhere that the user can get at it. What about the other end of the process?

Reconstituting the Settings Object

Because I want the persisted settings to be applied as soon as the form is loaded, I've placed the code in the Form's Load event handler:

private void Form1_Load(object sender, System.EventArgs e)
{
	try
	{
		// Get the isolated store for this assembly
		IsolatedStorageFile isf = 
			IsolatedStorageFile.GetUserStoreForAssembly();

		// Open the settings file
		IsolatedStorageFileStream isfs1 = 
			new IsolatedStorageFileStream("HTMLBuilderSettings.xml", 
			FileMode.Open, FileAccess.Read, isf);

		// Deserialize the XML to an object
		Settings s = new Settings();
		SoapFormatter SF= new SoapFormatter();
		s = (Settings) SF.Deserialize(isfs1);
		isfs1.Close();

		// And apply the settings to the form
		this.Top = s.Top;
		this.Left = s.Left;
		this.Height = s.Height;
		this.Width = s.Width;
		txtName.Text=s.Name;
		txtEmail.Text=s.Email;

	}
	catch (Exception ex)
	{
		// No file found. Use the form's default settings
	}

}

A little inspection should convince you that the loading code is just the reverse of the saving code. Open the settings file, deserialize the XML to an object, and then pick apart the object's public fields to get at the persisted information. If anything goes wrong, the form will just open with its default settings (the size and contents that were saved in the designer when the application was compiled.

Code You Can Use

That's all there is to it! To use this code in your own applications, you just need to alter the Settings object so that it has public fields for each piece of information that you'd like to persist. Then read from those fields when the application starts, and write to them when it ends. Your users will thank you for the extra effort.

About the Author

Mike Gunderloy is the author of over 20 books and numerous articles on development topics, and the lead developer for Larkware. Check out his MCAD 70-305, MCAD 70-306, and MCAD 70-310 Training Guides from Que Publishing. When he's not writing code, Mike putters in the garden on his farm in eastern Washington state.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date