advanced web statistics

JSON Serialization with Silverlight Isolated Storage For the Win

8/19/2008 11:21:45 PM

I'm going to post this to help others who are killing themselves trying to figure out IsolatedStorage in Silverlight 2 Beta 2.  Ordinarily I'd post stuff like this for my own reference but I have had it beaten into my head for the past 12 straight hours.

The IsolatedStorageFile class abstracts the virtual file system for isolated storage.  It can be used to store stuff such as application settings or user login information.  The application settings being persisted on a user's machine were exactly what we needed to accomplish so that led me down this road.  I read numerous blogs, watched countless videos, and did endless searching and finally crafted my solution as a hodge-podge of all of them!

First, what didn't work.

Channel 9 had a video on "how easy it is to persist user application settings" using IsolatedStorage.  Their solution consisted of the following:

public class LocalStorageHelper
{
    private const string KEY = "FOO";

    public static object RetrieveObjectFromLocalStorage()
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains(KEY))
            return IsolatedStorageSettings.ApplicationSettings[KEY];

        return null;
    }

    public static void UpdateLocalStorageObject(object value)
    {
        if (IsolatedStorageSettings.ApplicationSettings.Contains(KEY))
            IsolatedStorageSettings.ApplicationSettings[KEY] = value;
        else
           
IsolatedStorageSettings.ApplicationSettings.Add(KEY, value);
    }
}

Simple enough.  The video presenter did all sorts of cool things like shut down the browser, open it back up, press F5 repeatedly and the storage still contained the user data.  I'm guessing this solution worked flawlessly in Beta 1.  I also had to add the checks for whether or not the ApplicationSettings contained the specified key.  Needless to say this implementation isn't very reliable.

It was then that I started looking into JSON and other storage options.  I remember in .NET 2.0 I was used to serializing objects into XML and thought I would take this route.  This was easier said than done because Silverlight is using a subset of the .NET Framework and the XML Serialization namespace didn't make it.  (NOTE: I could've used Linq XML but it was an after thought!).

Enter the DataContractJsonSerializer class.  Since we are only given a default storage of 1MB (more can be requested but the end-user has the option of shutting you down) JSON is a likely candidate since it isn't as verbose as XML.  A simple string written in a text file within the file store is more than sufficient enough to give us an instance of the object which holds the properties we want to persist.

Some code for converting an object to JSON string and vice-versa.

public static string ConvertObjectToJsonString(object input)
{
   try
   
{
      using (MemoryStream memoryStream = new MemoryStream())
      {
         DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(input.GetType());
         dataContractJsonSerializer.WriteObject(memoryStream, input);
         memoryStream.Position = 0;

         using (StreamReader streamReader = new StreamReader(memoryStream))
            return streamReader.ReadToEnd();
      }
   }
   catch (InvalidDataContractException)
   {
      return string.Empty;
   }
}

public static T ConvertJsonStringToObject<T>(string json)
{
   try
   
{
      using (MemoryStream memoryStream = new MemoryStream())
      {
         byte[] bytes = Encoding.Unicode.GetBytes(json);
         memoryStream.Write(bytes, 0, bytes.Length);
         memoryStream.Position = 0;

         DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(T));

         return (T)dataContractJsonSerializer.ReadObject(memoryStream);
      }
   }
   catch (SerializationException)
   {
      return default(T);
   }
}

You might notice the InvalidDataContractException I am catching above.  Like with XML Serialization you want to mark a class & properties as Serializable.  If you don't you won't be able to serialize it.  Hence the catch block.  Here's an example of a serializable class:

using System.Runtime.Serialization;

namespace LocalStorage.Objects
{
   [DataContract]
   public class UserAccount
   
{
      [DataMember] public string EmailAddress { get; set; }
      [DataMember] public string Password { get; set; }
   }
}

I'll show an example of how to store the user account in a second.  First how to open / create this in isolated storage.

public static void SaveDataToLocalStorage(string data, string filename)
{
   try
   
{
      using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
      {
         if (!isolatedStorageFile.FileExists(filename))
         {
            using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream(filename, FileMode.Create, isolatedStorageFile))
            using (StreamWriter streamWriter = new StreamWriter(isolatedStorageFileStream))
               streamWriter.Write(data);
         }
         else
         
{
            using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream(filename, FileMode.Open, isolatedStorageFile))
            using (StreamWriter streamWriter = new StreamWriter(isolatedStorageFileStream))
               streamWriter.Write(data);
         }
      }
   }
   catch (IsolatedStorageException)
   {      
   }
}

public static string RetrieveDataFromLocalStorage(string filename)
{
   try
   
{
      using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication())
      {
         if (!isolatedStorageFile.FileExists(filename))|
            return string.Empty;

         using (IsolatedStorageFileStream isolatedStorageFileStream = new IsolatedStorageFileStream(filename, FileMode.Open, isolatedStorageFile))
         using (StreamReader streamReader = new StreamReader(isolatedStorageFileStream))
            return streamReader.ReadToEnd();
      }
   }
   catch (IsolatedStorageException)
   {
      return string.Empty;
   }
}

I apologize for the mess but those are some pretty lengthy class names and descriptive variable names 8^)

So now how to use:

// instantiate our user
UserAccount user = new UserAccount
                              
{
                                 EmailAddress = "asdf@asdf.asdf"
                                 Password = "stupid"
                              
};

// convert to JSON string
string json = ConvertObjectToJsonString(user);
SaveDataToLocalStorage(json, "UserAccount.txt");

// convert back to UserAccount
string json = RetrieveDataFromLocalStorage("UserAccount.txt");

if (!string.IsNullOrEmpty(json))
{
   UserAccount account = ConvertJsonStringToObject<UserAccount>(json);
   // do stuff with userAccount here...
}

Now the user can be persisted on each site visit.  If you wanted to store the email address and password you could log your user in when they visit the application.  Think "remember me" checkbox or something similar.  Another good use would be to serialize the RSS categories and only display those articles from within your silverlight application.

The possibilities are endless.  Remember that this information is stored with the client in their AppData folder so don't put anything too private in there ;)

Easy.

UPDATE
If this is a little too cluttered for you to read might I suggest viewing the RSS feed.

C#, Silverlight

kick it on DotNetKicks.com

Comments


Many thanks for this post. Very helpful.

Your SaveDataToLocalStorage failed for me when writing less data than my previous write. It was not truncating the old data from the file. My revised version.

public static void SaveDataToLocalStorage(string data, string filename) {
try {
using (IsolatedStorageFile isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()) {
using (var isolatedStorageFileStream = new IsolatedStorageFileStream(filename, FileMode.Create, isolatedStorageFile)) {
using (var streamWriter = new StreamWriter(isolatedStorageFileStream)) {
streamWriter.Write(data);
}
}
}
}
catch (IsolatedStorageException) {}
}

Posted by: Grateful Reader | 9/2/2008 1:05:37 AM

Thanks for the comment Grateful Reader!

Posted by: Will Asrari | 9/3/2008 5:41:31 PM

Leave a Comment

   

  Enter the text to proceed!