I is for… Isolated Storage
Isolated storage is a pretty nifty concept that’s actually been part of the .NET Framework since version 1.1 but is getting a bit more attention these days with the adoption of Silverlight and the desire to leverage the client machine for a better user experience. Since Silverlight is a browser-based technology, you can’t just do things like access the file system, because that would break the “sandbox” model.
Enter isolated storage. In a nutshell, you can think of it as your own private (well, relatively) virtual file system. An IsolatedStorageFile
instance is essentially a container for directories and files associated with the current user and Silverlight application (or, optionally, web site). It’s intended as a way to store small bits of non-vital information associated with the application, like user-preferences.
Note, isolated storage is also part of the full .NET framework (to support Click-Once applications, for instance). The implementation for Silverlight, which is the focus of this post, is a subset of that functionality. Consult the MSDN documentation if you want more insight into capabilities of isolated storage on the full .NET Framework. Documentation on the Silverlight-specific implementation of isolated storage can be found on MSDN here.
Here are some of the key characteristics of isolated storage:
- It’s managed by the .NET runtime. You don’t specify where the isolated file store is stored on the disk; the runtime handles it, but it is part of the file system, typically under the AppData folder of your user profile, and so is discoverable. Hence, you don’t want use isolated storage for information that is required for your application to operate or thast contains sensitive information.
- There’s a 1MB default limit, although this can be increased if the client allows it. See how here.
- Storage is always associated with a particular user, and an application run by one user cannot access another’s storage. A given user, however, can be outfitted with unique isolated storage instances for each application (
GetUserStoreForApplication
) and a separate store shared by all applications served from a given site (GetUserStoreForSite
).
- Unlike the browser cache, isolated storage is persistent. The application can delete files or directories via methods of
IsolatedStorageFile
, and of course, the storage file could be removed by the user directly, via File Explorer for instance.
- You can use streams and serialization techniques to read and write data just as you do for a conventional file system.
Let’s work through a (contrived) sample, and you’ll get an idea of how easy it really is. I have a Silverlight application with two buttons which change the foreground color and background color respectively. Here’s a ‘before-and-after’ picture.
The code for this application is pretty simple:
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
cbBackground.Click += new RoutedEventHandler(cbBackground_Click);
cbForeground.Click += new RoutedEventHandler(cbForeground_Click);
}
void cbForeground_Click(object sender, RoutedEventArgs e)
{
txtHello.Foreground = new SolidColorBrush(GetRandomColor());
}
void cbBackground_Click(object sender, RoutedEventArgs e)
{
gridLayout.Background = new SolidColorBrush(GetRandomColor());
}
private Color GetRandomColor()
{
Random r = new Random((int)System.DateTime.Now.Ticks);
Color c = new Color();
c.A = 255;
c.R = (byte)r.Next(255);
c.G = (byte)r.Next(255);
c.B = (byte)r.Next(255);
return c;
}
}
The default experience is black text and a white background, but I want repeat visitors to retain their last set of color choices when they re-launch the application.
To save the color choices, I’ll make use of isolated storage. In the ‘real’ world, I’d probably use XML serialization to save my preferences, but here I’m taking the easy way out, and will store the colors as two hex strings representing the foreground color and background colors, respectively. For instance, the following contents would indicate opaque red text on a blue background:
FFFF0000
FF0000FF
Reading from Isolated Storage
To the program above, I’ve added some logic to read from the preferences file, colors.txt
, in isolated storage. Then I’ve set the foreground and background colors to the values read in (lines 10-12, below):
1: public partial class Page : UserControl
2: {
3: Color foreColor;
4: Color backColor;
5:
6: public Page()
7: {
8: InitializeComponent();
9:
10: LoadColors(out foreColor, out backColor);
11: txtHello.Foreground = new SolidColorBrush(foreColor);
12: gridLayout.Background = new SolidColorBrush(backColor);
13:
14: cbBackground.Click +=
15: new RoutedEventHandler(cbBackground_Click);
16: cbForeground.Click +=
17: new RoutedEventHandler(cbForeground_Click);
18: }
Here’s my implementation of LoadColors
.
1: public void LoadColors(out Color f, out Color b)
2: {
3: string foreStr = "00000000";
4: string backStr = "FFFFFFFF";
5:
6: using (IsolatedStorageFile isf =
7: IsolatedStorageFile.GetUserStoreForApplication())
8: {
9: try
10: {
11: using (IsolatedStorageFileStream str = isf.OpenFile
12: ("colors.txt", FileMode.Open, FileAccess.Read))
13: {
14: StreamReader reader = new StreamReader(str);
15: foreStr = reader.ReadLine();
16: backStr = reader.ReadLine();
17: reader.Close();
18: }
19: }
20: catch (IsolatedStorageException)
21: {
22: ;
23: }
24: }
25:
26: f = ToColor(foreStr);
27: b = ToColor(backStr);
28: }
Let’s take a line-by-line look:
3-4: Initialize two color strings to a default, should something go wrong.
6-7: Get the isolated storage associated with this user and this application (defined by the XAP file’s URL). If I wanted to share preferences across a suite of applications on the same host, I could have used
GetUserStoreForSite
here.11: Open a file with the name
colors.txt
in the isolated storage. If the file is not found, an exception will be thrown (and caught in Line 20). There is also a method I could have used to check for file existence instead of letting the exception catch it.14-17: Use a stream reader to read the two lines of the file.
20: Handle an access exception, including file-not-found. Here, we just let the default colors take over.
26-27: Convert the hex strings to color objects using a simple utility routine (below).
private Color ToColor(string hex) { if (hex == null) return Color.FromArgb(255, 0, 0, 0); return Color.FromArgb( (byte)(Convert.ToUInt32(hex.Substring(0, 2), 16)), (byte)(Convert.ToUInt32(hex.Substring(2, 2), 16)), (byte)(Convert.ToUInt32(hex.Substring(4, 2), 16)), (byte)(Convert.ToUInt32(hex.Substring(5, 2), 16))); }
Writing to Isolated Storage
Now, for the other direction: writing modifications to the user preferences to isolated storage. For that I’ve created another method, SaveColors
, which is called in each of the two button Click event handlers, after setting the new color to a randomly generated color. The implementation of SaveColors
is as follows:
1: public void SaveColors(Brush fBrush, Brush bBrush)
2: {
3: using (IsolatedStorageFile isf =
4: IsolatedStorageFile.GetUserStoreForApplication())
5: {
6: using (FileStream str = isf.OpenFile("colors.txt",
7: FileMode.OpenOrCreate, FileAccess.Write))
8: {
9: StreamWriter writer = new StreamWriter(str);
10: writer.WriteLine(
11: FromColor(((SolidColorBrush) fBrush).Color));
12: writer.WriteLine(
13: FromColor(((SolidColorBrush) bBrush).Color));
14: writer.Close();
15: }
16: }
17: }
3-4: Obtain the isolated storage for this user and application.
6-7: Open (or create) the
colors.txt
file for writing.10-13: Write the new color strings to the file.
FromColor
is another utility routine to convert a color to its hex string equivalent. It’s implemented as a single line of code!private String FromColor(Color c) { return String.Format( "{0:x2}{1:x2}{2:x2}{3:x2}", c.A, c.R, c.G, c.B); }
Managing Isolated Storage Limits
The IsolatedStorageFile
class has two properties to return the size of the store (Quota
) as well as the available free space (AvailableFreeSpace
). With that information you can test whether or not there’s sufficient space for whatever you’re planning to store next.
If there isn’t, you can call the IncreaseQuotaTo
method to extend the amount of isolated storage space. There are a few caveats when using this method to be aware of
- You can only call this method in response to a user action, like a button Click event; otherwise, as a security precaution, it’s ignored.
- You can’t decrease the quota (without removing the store and recreating it).
- You will get an exception if you try to increase the quota to a value smaller than the current size; therefore, use the
Quota
andAvailableFreeSpace
properties to check before you ask!
Presuming the request is a valid one, the end user will be presented a dialog like the following:
The boolean return value of IncreaseQuotaTo
is determined by the user’s response to this dialog.
You can also see how much isolated storage Silverlight has associated with the current user – across ALL of the Silverllight applications he or she has browsed – by viewing the Application Storage tab on the Silverlight Configuration dialog. You can get to this dialog by right-clicking on any web page currently hosting Silverlight content and selecting the Silverlight Configuration option from the context menu.
A couple of closing thoughts:
1.
IsolatedStorageFile
has a number of other methods to allow you to create, remove, and check for the existence of files and directories within the store.2. The
IsolatedStorageSettings
class is a convenient way to store simple key-value pairs in isolated storage. For the example above, it would have been a very appropriate option (but less interesting to discuss!)2. You can figure out where the actual file store is by setting a breakpoint in your code after the
IsolatedStorageFile
reference has been obtained. Look at the value of theRootDirectory
property.When I was running the sample above, here’s what mine was set to:
C:\Users\joneil\AppData\LocalLow\Microsoft\Silverlight\is \23zwbqug.imk\r3q1ieeo.odx\1\s\f2jzlyu1vrz2g5zcgk0moakn1 pdsiwrcpx22ardrblfykpxui4aaahba\f
Comments
Anonymous
March 23, 2010
Jim, It's not necessary to manage the FileStream Here is a simple String ISO nugget public static string Get(string Key) { if (IsolatedStorageSettings.ApplicationSettings.Contains(Key)) return IsolatedStorageSettings.ApplicationSettings[Key] as string; else return string.Empty; } public static void Add(string Key, string Value) { IsolatedStorageSettings.ApplicationSettings[Key] = Value; IsolatedStorageSettings.ApplicationSettings.Save(); }Anonymous
March 24, 2010
That's a great point, Pat, especially for the contrived example I came up with.