It's not unusual for an application to need to store some data for later use; maybe your application allows the user to set persistent options or save work in progress. But even the simple act of saving data can be fraught with dangers, in today's world. Consider these potential stumbling blocks:
In the past, developers have dealt with these issues using a variety of types of special-purpose code that attempts to create unique disk files or registry keys. When you add the differing disk layouts of different operating systems, user-level security, and roaming user profiles into the mix, this becomes an extremely difficult task. Fortunately, this is an area where the .NET Framework offers a new and better way to handle things. This new and better way is called isolated storage.
Isolated storage assigns each user of the system an abstract location called a data compartment. Within the data compartment, there can be one or more stores. Stores can be isolated from one another by both user and assembly. That is, if two users run the same assembly, and that assembly uses isolated storage, then the two stores created are distinct from one another. Similarly, if the same user runs two different assemblies, and the assemblies use isolated storage, then the two stores created are distinct from one another.
A store acts as a complete virtual file system. You can create and delete directories and files within a store, and read and write data to the files in the store. As a practical matter, the data from a store is kept in hidden files in the file system, as shown in Figure 1, but you don't need to worry about that when using the isolated storage interfaces. The .NET Framework takes care of the details of finding a file system location that the user has permission to use.
|Figure 1. The .NET Framework uses obscure folder names for isolated storage.|
If you're familiar with the streams and stores model used by most I/O operations under the .NET Framework, you should find isolated storage code relatively easy to understand. I'll show you the code for some basic isolated-storage operations, followed by a more application-oriented example.
To work with isolated storage, you'll first need to include the appropriate namespace declarations in your code:
Imports System.IO Imports System.IO.IsolatedStorage
Isolated storage stores are represented by the
IsolatedStorageFile object. There's a static method of this object
that you can use to obtain the store for the current assembly:
' Get the isolated store for this assembly Dim isf As IsolatedStorageFile = _ IsolatedStorageFile.GetUserStoreForAssembly()
IsolatedStorageFile object implements a
CreateDirectory method that you can use to create directories
within the store:
' Create a directory at the top level of the store isf.CreateDirectory("Dir1")
You can create subdirectories by specifying the entire path to the subdirectory in the call. The parent directory will be created by the same call, if necessary. Note that URL-style forward slashes are used to separate directories and subdirectories:
' Create a subdirectory isf.CreateDirectory("Dir1/Dir2")
Files inside of an isolated storage store are represented by an
IsolatedStorageFileStream object. To create or open a file, you can
call the constructor for this class with appropriate parameters. You can create
a new file at any level in the directory hierarchy, but the directories must
' Create a file at the top level of the store Dim isfs1 As IsolatedStorageFileStream = _ New IsolatedStorageFileStream("Rootfile.txt", _ FileMode.OpenOrCreate, FileAccess.Write, isf)
There are several overloaded constructors for the
IsolatedStorageFileStream class, but you're most likely to use the
one shown here. It takes four parameters:
FileModeconstant indicating the desired action. This can be
Create(which overwrites any existing file),
CreateNew(which throws an exception if the file exists),
Truncate(open an existing file and set its size to zero).
FileAccessconstant indicating the desired access. This can be
IsolatedStorageFileobject that represents the store where this file will be located.
After you've created an
IsolatedStorageFileStream to represent a
file, writing to the file is exactly like writing to any other stream. At this
point, you can use any of the classes in the
(or elsewhere) that write to streams. For example, you can use a
StreamWriter class to write text directly into the file:
' Create or open a file at the top level of the store Dim isfs1 As IsolatedStorageFileStream = _ New IsolatedStorageFileStream("Rootfile.txt", _ FileMode.OpenOrCreate, FileAccess.Write, isf) ' Treat it like any other stream Dim sw As StreamWriter = New StreamWriter(isfs1) sw.WriteLine("Isolated storage is keen.") sw.WriteLine("You can treat it like a file.") sw.Flush() sw.Close()
Reading from an isolated storage file is similar to reading from any other
stream. For example, you can use a
StreamReader object to extract
information from the file, either line by line, or character by character:
' Open a file at the top level of the store Dim isfs1 As IsolatedStorageFileStream = _ New IsolatedStorageFileStream("Rootfile.txt", _ FileMode.Open, FileAccess.Read, isf) ' Treat it like any other stream Dim sr As StreamReader = New StreamReader(isfs1) Dim sw As StringWriter = New StringWriter() While (sr.Peek() > -1) sw.WriteLine(sr.ReadLine) End While MessageBox.Show(sw.ToString, _ "Isolated Storage contents")
IsolatedStorageFile class also provides a method to delete a
file in which you are no longer interested:
' Delete some files isf.DeleteFile("Dir3/Dir4/Anotherfile.txt") isf.DeleteFile("Rootfile.txt")
You can also delete directories by using the
method. Note that you must delete nested directories one at a time, starting
with the most deeply nested directory.
' Delete some directories isf.DeleteDirectory("Dir1/Dir2/") isf.DeleteDirectory("Dir1/")
As I mentioned at the start of the article, one common use of isolated storage is to save user settings. To demonstrate this, I'll show you how to equip a form with size and location persistence. Although you can stick anything you want in isolated storage, in this case I'll take advantage of the .NET Framework's hooks for easy XML serialization and deserialization of objects by starting with a class to hold the information that I want to save:
<Serializable()> _ Public Class FormSettings Public Top As Integer Public Left As Integer Public Height As Integer Public Width As Integer End Class
The basic strategy here is to create and save a
object when the form is closed, and to retrieve the saved object when it's
opened. Here's the creation code, which I've placed in the form's
Closing event handler so that it will be called whenever the user
is finished with the form:
Private Sub Form1_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) _ Handles MyBase.Closing Try ' Get the isolated store for this assembly Dim isf As IsolatedStorageFile = _ 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 Dim isfs1 As IsolatedStorageFileStream = _ New IsolatedStorageFileStream("FormSettings.xml", _ FileMode.Create, FileAccess.Write, isf) ' Construct an object and tell it about the form Dim fs As FormSettings = New FormSettings fs.Top = Me.Top fs.Left = Me.Left fs.Height = Me.Height fs.Width = Me.Width ' Serialize the object to the file Dim xs As XmlSerializer = _ New XmlSerializer(GetType(FormSettings)) xs.Serialize(isfs1, fs) isfs1.Close() Catch ex As Exception ' If settings can't be saved, next ' run will just the use form defaults End Try End Sub
The other end of the process happens when the form is loaded:
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Try ' Get the isolated store for this assembly Dim isf As IsolatedStorageFile = _ IsolatedStorageFile.GetUserStoreForAssembly() ' Open the settings file Dim isfs1 As IsolatedStorageFileStream = _ New IsolatedStorageFileStream("FormSettings.xml", _ FileMode.Open, FileAccess.Read, isf) ' Deserialize the XML to an object Dim fs As FormSettings = New FormSettings Dim xtr As XmlTextReader = New XmlTextReader(isfs1) Dim xs As XmlSerializer = _ New XmlSerializer(GetType(FormSettings)) fs = CType(xs.Deserialize(xtr), FormSettings) ' And apply the settings to the form Me.Top = fs.Top Me.Left = fs.Left Me.Height = fs.Height Me.Width = fs.Width ' Clean up isfs1.Close() Catch ex As Exception ' No file found. Just run with the existing settings End Try End Sub
To see this code in action, create a new Visual Basic .NET Windows
application, add the
FormSettings.vb class, and place the event
code behind the form. You'll need to add references to the appropriate
namespaces as well:
Imports System.IO Imports System.IO.IsolatedStorage Imports System.Xml Imports System.Xml.Serialization
Then run the application to display the default blank form. Move it anywhere on the screen and size it as you like. Close the form to exit the application. Now run it again. You'll find that the form comes back where you left it, instead of at its default location.
Note that very little of this code is related to the actual settings that I'm storing. If I decided in the future to add the form's background color, its caption text, or any other property to the list of things to persist, I could simply add another public property to the class and one line in each event procedure. The isolated storage and serialization plumbing takes care of everything else.
As with any other technique, there are good times and bad times to use isolated storage. Here are some typical applications where isolated storage can be a good fit:
But while you're considering isolated storage, you also need to think about the potential drawbacks and pitfalls:
IsolatedStorageFilePermissionto work with isolated storage. This can be a problem if your machine is severely locked down by administrative security policies.
I think you'll find it worth taking the time to understand isolated storage, especially if you still recall the joy of trying to find a file or registry location that worked as a universal place to save settings in previous development environments. In this, as in so many other ways, the .NET Framework has done an admirable job of providing functionality that just works the way it should.
Mike Gunderloy is the lead developer for Larkware and author of numerous books and articles on programming topics.
Return to ONDotnet.com
Copyright © 2009 O'Reilly Media, Inc.