Published on

Storage Helper for WinRT (Win8 Metro) in C#

Authors
  • avatar
    Name
    Jac Timms
    Twitter

Windows 8 Metro Storage Helper

I'm currently coding a Windows 8 Metro app version of a Windows Phone 7.5 Mango app I previously made. There are quite a few differences in the code and one of the first stumbling blocks I hit was the lack of Isolated Storage in WinRT. There are some ways of making shared libraries compatible, but I wanted to learn the new ways of doing things. One of the things I need to do is store POCOs to storage for caching and while having a quick search to see if someone had any examples, I came across Generic Object Storage Helper for WinRT by Jamie Thomson, which was a great help. The code is out of date now however as it was written for the Developer Preview and I'm coding against the Consumer Preview. Also the example also isn't quite what I wanted as I want to be able to pass in a file-name each time, so I decided to modify and fix the code as well as make some changes to make it the way I like.

Here is my updated version of the class:

using System;
using System.IO;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Windows.Storage;
using Windows.Storage.Streams;

namespace MyWinRTApp
{
    public enum StorageType { Local, Temporary, Roaming 
}

class IsoStorage<T>
{
    private ApplicationData appData = Windows.Storage.ApplicationData.Current;
    private XmlSerializer xmlSerializer;
    private StorageFolder storageFolder;
    private StorageType storageType;
    public StorageType StorageType {
        get { return storageType; }
        set
        {
            storageType = value;
            // set the storage folder
            switch (storageType)
            {
                case StorageType.Local:
                    storageFolder = appData.LocalFolder;
                    break;
                case StorageType.Temporary:
                    storageFolder = appData.TemporaryFolder;
                    break;
                case StorageType.Roaming:
                    storageFolder = appData.RoamingFolder;
                    break;
                default:
                    throw new Exception(String.Format("Unknown StorageType: {0}", storageType));
            }
        }
    }

    public IsoStorage() : this(StorageType.Local) {}
    public IsoStorage(StorageType type)
    {
        xmlSerializer = new XmlSerializer(typeof(T));
        StorageType = type;
    }

    /// <summary>
    /// Saves a serialized object to storage asynchronously
    /// </summary>
    /// <param name="filename"></param>
    /// <param name="obj"></param>
    public async void SaveAsync(string fileName, T data)
    {
        try
        {
            if (data == null)
                return;
            fileName = AppendExt(fileName);
            var file = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
            var writeStream = await file.OpenAsync(FileAccessMode.ReadWrite);
            var outStream = Task.Run(() => writeStream.AsStreamForWrite()).Result;
            xmlSerializer.Serialize(outStream, data);
            writeStream.Dispose();
        }
        catch (Exception)
        {
            throw;
        }
    }

    /// <summary>
    /// Delete a file asynchronously
    /// </summary>
    /// <param name="fileName"></param>
    public async void DeleteAsync(string fileName)
    {
        try
        {
            fileName = AppendExt(fileName);
            var file = await GetFileIfExistsAsync(fileName);
            if (file != null)
                await file.DeleteAsync();
        }
        catch (Exception)
        {
            throw;
        }
    }

    /// <summary>
    /// At the moment the only way to check if a file exists to catch an exception... :/
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    private async Task<StorageFile> GetFileIfExistsAsync(string fileName)
    {
        try
        { return await storageFolder.GetFileAsync(fileName); }
        catch
        { return null; }
    }

    /// <summary>
    /// Load a given filename asynchronously
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public async Task<T> LoadAsync(string fileName)
    {
        try
        {
            fileName = AppendExt(fileName);
            StorageFile file = null;
            file = await storageFolder.GetFileAsync(fileName);
            IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
            Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result;
            return (T)xmlSerializer.Deserialize(inStream);
        }
        catch (FileNotFoundException)
        {
            //file not existing is perfectly valid so simply return the default 
            return default(T);
            //throw;
        }
        catch (Exception)
        {
            //Unable to load contents of file
            throw;
        }
    }

    /// <summary>
    /// Appends the file extension to the given filename
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    private string AppendExt(string fileName) {
        if (fileName.Contains(".xml"))
            return fileName;
        else
            return string.Format("{0}.xml", fileName);
        }
    }
}

It seems that at the moment, there is no way to check if a file exists without using a try/catch block, but I expect that will be sorted in the final release.

Saving an Object

Saving a POCO (any serializable object) is as simple as:

var forecast = new Forecast { Date = DateTime.Now, Type = FeedType.Daily };
var isoStorage = new IsoStorage<Forecast>(StorageType.Local);
isoStorage.SaveAsync("daily", forecast);

You could also save a generic list by doing something like

var isoStorage = new IsoStorage<List<Forecast>>(StorageType.Local);

Retrieving an Object

Retrieving a POCO is equally easy:

var isoStorage = new IsoStorage<Forecast>(StorageType.Local); Forecast forecast = await isoStorage.LoadAsync("daily");

I hope that helps someone. Please feel free to leave feedback or comments below. Big thanks go out to Jamie Thomson for the initial code that I modified.