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.