12 January 2011

Tombstoning MVVMLight ViewModels with SilverlightSerializer on Windows Phone 7

I’ve been down this road before but I thought it wise to revisit this subject, since I see a lot of people in the Windows Phone 7 developer community still struggling with the concept of tombstoming. In my previous attempt to make some universal way of tombstoning ViewModels on Windows Phone 7 I used DataContractSerializer, which is a nice idea but does not work very well with MVVMLight since the ViewModelBase is not serializable. And sometimes you have to go trough a lot of hooplah if stuff you use in your code does not turn out to be serializable after all.There is a better way, I think. So here it is: universal tombstoning for MVVMLight, take two.

Enter SilverlightSerializer by the as far as I am concerned immortal Mike Talbot. Download this thing and store it somewhere in your sources. To use it for tombstoning, I have created the following extension methods:

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using GalaSoft.MvvmLight;

namespace LocalJoost.Utilities
{
  public static class ApplicationExtensions
  {
    private static string GetIsFile( Type t)
    {
      return string.Concat(t.Name, ".dat");
    }

    public static T RetrieveFromIsolatedStorage<T>(this Application app) 
      where T : class
    {
      using (var userAppStore = 
         IsolatedStorageFile.GetUserStoreForApplication())
      {
        var dataFileName = GetIsFile(typeof(T));
        if (userAppStore.FileExists(dataFileName))
        {
          using (var iss = userAppStore.OpenFile(dataFileName, FileMode.Open))
          {
             return SilverlightSerializer.Deserialize(iss) as T;
          }
        }
      }
      return null;
    }

    public static void SaveToIsolatedStorage(this Application app, 
                                      ViewModelBase model)
    {
      var dataFileName = GetIsFile((model.GetType()));
      using (var userAppStore = 
               IsolatedStorageFile.GetUserStoreForApplication())
      {
        if (userAppStore.FileExists(dataFileName))
        {
          userAppStore.DeleteFile(dataFileName);
        }
        using (var iss = userAppStore.CreateFile(dataFileName))
        {
          SilverlightSerializer.Serialize(model,iss);          
        }
      }
    }
  }
}

Then I go to the App.xaml.cs and modify it as depicted next:

private void Application_Launching(object sender, LaunchingEventArgs e)
{
  LoadModel();
}

private void Application_Activated(object sender, ActivatedEventArgs e)
{
  LoadModel();
}

private void LoadModel()
{
  try
  {
    MyMainViewModel.Instance = this.RetrieveFromIsolatedStorage<MyMainViewModel>();
  }
  catch (Exception){ }
  if( MyMainViewModel.Instance == null) MyMainViewModel.CreateNew(); 
}

private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  this.SaveToIsolatedStorage(MyMainViewModel.Instance);
}

private void Application_Closing(object sender, ClosingEventArgs e)
{
  this.SaveToIsolatedStorage(MyMainViewModel.Instance);
}

In red you see my additions to the standard generated App.xaml.cs. You will also need to add at least one “using” statement (using LocalJoost.Utilities) and a second one with the namespace in which you have put MyMainViewModel. Notice the fact that there is an empty try-catch around the RetrieveFromIsolatedStorage call. This is intentional. Since binary deserialization tends to throw exceptions if you have changed the ViewModel’s code, you always want to make sure your application gets served a working ViewModel where ever it needs to come from.

The skeleton of a MainViewModel looks something like this in my household:

public class MyMainViewModel : ViewModelBase
{
  public MyMainViewModel( )
  {
  }  

  private static MyMainViewModel _instance;
  public static MyMainViewModel Instance
  {
    get {return _instance;}
    set { _instance = value; }
  }

  public static void CreateNew()
  {
    if (_instance == null)
    {
      _instance = new MyMainViewModel();
    }
  }
}

If your want certain properties of your ViewModel specifically not to be serialized (because they come from a – in this example omitted – application model) you can mark them with the [DoNotSerialize] attribute that comes with SilverlightSerializer.

You can now always bind to MyMainViewModel.Instance and you will either get a fresh new or a retrieved from isolated storage ViewModel. The fun thing is that although MVVMLight’s ViewModelBase is not serializable, the SilverlightSerializer does in fact serialize it which makes life a whole lot easier.

You can, by the way, also decide to store the ViewModel on the PhoneApplicationService on deactivation and retrieve it there on activation. IMHO always storing on isolated storage gives a more consistent feeling to an application. But that’s just me.

A few important points to conclude this post:

  • A ViewModel that is to be serialized on isolated storage should always have a default constructor.
  • Properties of the ViewModel that you want to have serialized need to have a getter and a setter (believe me, you can spend some time overlooking this).
  • Do not try something clever like this on the static instance property of your ViewModel:
public static MyMainViewModel Instance
{
  get {return _instance ?? (_instance = new MyMainViewModel());}
  set { _instance = value; }
}
If you do this, MyMainViewModel.Instance = this.RetrieveFromIsolatedStorage will first call the getter, thus creating a new instance of MyMainViewModel whatever happens next, then overwrite it with the retrieved version. But if you do something like registering a message in the constructor of MyMainViewModel, the initially created model will stay alive ‘somewhere’ - you will end up with two instances of your supposedly singleton instance running in you App, both listening to the same message and reacting to it - and spend, like me, a not so nice and probably extended period of  time scratching your head and wondering what the hell is going on.

I hope that with this post I once and for all made it easier for the Windows Phone 7 community to tackle something apparently tricky as tombstoning thus improving the quality of what we are all going to put in the App Hub.

11 comments:

peSHIr said...

The "if( MyMainViewModel.Instance ) == null MyMainViewModel.CreateNew();" seems to be in two minds. CreateNew or null?

Loc#alJoost said...

My word. You are right. Thanks

Sankar said...

hi, Could you please provide sample example on "Tombstoning MVVMLight ViewModels with SilverlightSerializer on Windows Phone 7".Please provie me as soon as possible thank you..

Loc#alJoost said...

@Sankar, if you check this article http://dotnetbyexample.blogspot.com/2011/04/bing-maps-control-with-mvvmlight-on.html you will find this
http://www.schaikweb.net/dotnetmag/MapBindingDemo.zip link at the bottom that shows how I implement tombstoning with SilverlightSerializer. Good luck!

Matthias said...

Hopefully not a stupid question, but "this.RetrieveFromIsolatedStorage()" is giving me a "the type arguments for method [blah blah blah] cannot be inferred from the usage" error.

Surely, I'm missing something very simple.

Loc#alJoost said...

Indeed Matthias you are missing something very simple error, but unfortunately it was one I made myself. The this.RetrieveFromIsolatedStorage() must be this.RetrieveFromIsolatedStorage<MyMainViewModel>() It was there in text, but I apparently forgot URLescape the sample code. My bad. Thanks for noticing this

Priya said...

Thanks for the great article. I do have a question related to the place where you're restoring the VM. As you know, in MVVMLight, one typically relies on the ViewModelLocator to make VM instances available. How would that tie in with the whole tombstoning thing? Or are you not using the ViewModelLocator in your example?
Thanks,
Priya

Loc#alJoost said...

@Priya thanks for you kind comments. I indeed use a kind of ViewModelLocator, usually called MainViewModel. That more or less creates all the other ViewModels on demand. But there are some ViewModels that exist on their own, like my AboutViewModel, that is simply created by XAML as a static resource.

@awsomedevisger said...

Hi Joost, I like the way you handle the stuff with extension methods :) I would like to think about a more flexible approach to add maybe additional methods to save the stuff to a database. Would you suggest creating a service for that, or suggest to add only additional methods? I would tend to a ITombstoneHelper to implement the stuff. Thank you.

Joost van Schaik said...

Hi @awsomedevisger I would go for an interface as well. Personally I would instantiate the implementation of your ITombstoneWhatever in the App.Xaml.cs and then store it in Laurent Bugnion's SimpleIoc so you can retrieve it everywhere. Dunno if that's a service. It works ;-)

@awsomedevisger said...

Hi Joost, thank you for the feedback! I will let you know, which solution I will spit out in the next few days. I looked also at the Sterling OODB from Jeremy Likness, which he - if he has time - will update also for WP8 and I want to see, if I can create serializers for standard WP7 and 8 controls, where it makes sense. Best, Ilija