Isolated storage is a file system. It is "isolated" because it is rooted to a specific directory, which provides the sandbox needed for browser security.
Get a pointer to the file system
There are two types of file systems in Silverlight:
- IsolatedStorageFile.GetUserStoreForApplication() - This provides a file system specific to an application. From a development standpoint, you can consider the file system unique to each XAP.
- IsolatedStorageFile.GetUserStoreForSite() - This provides a file system specific to the site. This could be used to share data between XAPs on the same site.
Generic repository for persisting the ViewModel
Creating a base generic repository allowed me to centralize common IsolatedStorageFile operations. These could be needed any time. I wanted to save, retrieve, or delete any type of object from Isolated Storage in my application, while still being strongly typed.
public abstract class TIsolatedStorageRepository<T>
{
private readonly string _filename;
protected TIsolatedStorageRepository(string filename)
{
_filename = filename;
}
protected abstract IsolatedStorageFile GetUserStore();
public IEnumerable<T> SelectAll()
{
var items = new List<T>();
try
{
using (var filesystem = GetUserStore())
using (var filestream = new IsolatedStorageFileStream(_filename, FileMode.OpenOrCreate, filesystem))
{
if (filestream.Length == 0)
return items;
var transform = new XmlSerializer(typeof (List<T>));
items = (List<T>) transform.Deserialize(filestream);
}
}
catch
{
}
return items;
}
public void Insert(T item)
{
try
{
var items = new List<T>();
using (var filesystem = GetUserStore())
using (var filestream = new IsolatedStorageFileStream(_filename, FileMode.OpenOrCreate, filesystem))
{
var transform = new XmlSerializer(typeof (List<T>));
if (filestream.Length != 0)
{
items = (List<T>) transform.Deserialize(filestream);
}
filestream.Flush();
filestream.SetLength(0);
filestream.Seek(0, SeekOrigin.Begin);
items.Add(item);
transform.Serialize(filestream, items);
}
}
catch
{
}
}
public void Delete(T item)
{
try
{
var items = new List<T>();
using (var filesystem = GetUserStore())
using (var filestream = new IsolatedStorageFileStream(_filename, FileMode.OpenOrCreate, filesystem))
{
var transform = new XmlSerializer(typeof (List<T>));
if (filestream.Length != 0)
{
items = (List<T>) transform.Deserialize(filestream);
}
var matches = items.Where(i => i.Equals(item));
var keep = items.Except(matches).ToList();
filestream.Flush();
filestream.SetLength(0);
filestream.Seek(0, SeekOrigin.Begin);
transform.Serialize(filestream, keep);
}
}
catch
{
}
}
}
How do I use it?
Following a view first approach when designing with MVVM. Lets review the goal.

In this UI, the user scans items into a shopping cart, which requires the user to explicitly call 'Process Order' when the batch is ready to be processed. The user can accidentally close the browser at any time, or the browser could freeze. Several minutes of manually scanning could be lost this way.
So the goal is to have the 'Cart' populated from Isolated Storage on initial load. As records are added and removed we have to keep the 'Cart' collection in sync with the collection represented in Isolated Storage.
Here is the excerpt from the viewmodel that we need to implement this interaction (Using PRISM DelegateCommand). The trick is to attach to the CollectionChanged event, which has been exposed by the ObservableCollection.
private readonly IUnityContainer _unityContainer;
private readonly CartIsolatedStorageRepository _cartRepository;
public SampleViewModel(IUnityContainer unityContainer, CartIsolatedStorageRepository cartRepository)
{
_unityContainer = unityContainer;
_cartRepository = cartRepository;
Cart = new ObservableCollection<AssetViewModel>();
foreach (var asset in _cartRepository.SelectAll())
{
var assetviewmodel = _unityContainer.Resolve<AssetViewModel>();
assetviewmodel.Load(asset);
Cart.Add(assetviewmodel);
}
Cart.CollectionChanged += Cart_CollectionChanged;
ProcessCartCommand = new DelegateCommand<object>(ProcessOrderCommandExecute);
AddAssetCommand = new DelegateCommand<object>(AddAssetCommandExecute, AddAssetCommandCanExecute);
DeleteAssetCommand = new DelegateCommand<object>(DeleteItemCommandExecute);
}
void Cart_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (AssetViewModel newItem in e.NewItems)
{
_cartRepository.Insert(newItem.Asset);
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (AssetViewModel oldItem in e.OldItems)
{
_cartRepository.Delete(oldItem.Asset);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public ObservableCollection<AssetViewModel> Cart
{
get;
private set;
}
NotifyCollectionChangedEventArgs Action also has NotifyCollectionChangedAction.Reset, called when the following assignment is made:
Cart = new ObservableCollection<AssetViewModel>();
Cart[0] = _unityContainer.Resolve<AssetViewModel>();
The benefit of using the generic repository can be seen by the concrete Isolated Storage class we created for this view.
public class CartIsolatedStorageRepository : TIsolatedStorageRepository<Asset>
{
public CartIsolatedStorageRepository()
: base("Cart.xml")
{
}
protected override IsolatedStorageFile GetUserStore()
{
return IsolatedStorageFile.GetUserStoreForApplication();
}
}
Extending
Our current view does not allow record editing. This is why the generic repository does not contain an update. To implement an update, the developer would need to subscribe to the PropertyChanged event of the ViewModel being persisted, Asset, and ensure 'Equals' compare the primary key solely for equality.
Here is how Equals needs to be written with some help from Resharper.
#if SILVERLIGHT
public class Asset : INotifyPropertyChanged, IEquatable<Asset>
{
private string _barcode = String.Empty;
public string Barcode
{
get { return _barcode; }
set { _barcode = value; RaisePropertyChanged("Barcode"); }
}
private int _quantity;
public int Quantity
{
get { return _quantity; }
set { _quantity = value; RaisePropertyChanged("Quantity"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != typeof (Asset)) return false;
return Equals((Asset) obj);
}
public bool Equals(Asset other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other._barcode, _barcode) && other._quantity == _quantity;
}
public override int GetHashCode()
{
unchecked
{
return ((_barcode != null ? _barcode.GetHashCode() : 0)*397) ^ _quantity;
}
}
public static bool operator ==(Asset left, Asset right)
{
return Equals(left, right);
}
public static bool operator !=(Asset left, Asset right)
{
return !Equals(left, right);
}
}
#endif
Things to be aware of
This repository is non transactional. Setting filestream.SetLength(0); will be committed, but an exception might occur during Serialize process, thereby deleting existing records
Please note that these methods in generic repository create exceptions. This is deliberate, as Isolated Storage is not guaranteed and can throw exceptions based on user specific quota limits.
While Isolated Storage is nice to have, it should not cause breaks in my application. I these methods are functionally flawed, then we might do better avoiding them.

Gareth Watt
# June 1, 2010 - 5:07 AM
Leblanc Meneses
# July 7, 2010 - 4:47 AM
Michael Washington
# June 1, 2010 - 5:43 AM
Leblanc Meneses
# July 7, 2010 - 4:47 AM
Leblanc Meneses
# July 9, 2010 - 4:34 PM