I is for… Isolated Storage

iIsolated 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.

image image

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 and AvailableFreeSpace properties to check before you ask!

Presuming the request is a valid one, the end user will be presented a dialog like the following:

image

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.

image

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 the RootDirectory 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