.NET Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Porting Visual Studio Achievements for WP to Windows 8 - Serialization and Storage

03.24.2012
| 3993 views |
  • submit to reddit

I am getting back to my series of articles describing how to port Visual Studio Achievements for Windows Phone to Windows 8. Just in case you are interested in the previous articles, here are the links:

The main application engine is working - I am successfully able to add users that I want tracked to the list. That list, however, is volatile. Once the application is closed, I no longer have access to the previous data. Normally, this wouldn't be a problem in cases where only one user is tracked. When there are many, however, it is necessary to make sure that the data is preserved and re-used on startup.

In my Windows Phone application, I use the following method to serialize the Niners (Channel9 users):

public static void SerializeNiners()
{
    var userStore = IsolatedStorageFile.GetUserStoreForApplication();

    using (var stream = new IsolatedStorageFileStream("niners.xml", FileMode.Create, userStore))
    {
        XmlSerializer serializer = new XmlSerializer(BindingPoint.Instance.Niners.GetType());
        serializer.Serialize(stream, BindingPoint.Instance.Niners);
    }
}

This relies on the concept of isolated storage - each Windows Phone application has a designated area where the application can store files. That area is not accessible from outside the boundaries of that application. With WinRT, Metro applications do not rely on isolated storage, but rather on the designated areas in the user disk space that the application is allowed to access.

For VSA, I decided that I will use the Documents library as the primary storage endpoint. Open Package.appxmanifest and go to the Capabilities tab. Make sure that the Documents Library option is selected:

With Metro applications there is one more restriction. An application can only handle specific file types if there is a registered file association for it. Therefore, since I am handling XML files, I need to set up a file type association. In the same Package.appxmanifest, open the Declarations tab. From the list of available declarations, select File Type Associations and click on Add.

Make sure that you specify the content and file types. Once that is done, the application is ready to handle XML files.

I created a new folder in the solution called Util. It is designed to keep classes that can be useful in various parts of the app, including the front-end and the backend. I added a new class called DataSerializer. It is static and it has two methods - SerializeNiners and DeserializeNiners, both decorated with async. Let's take a look at how the serialization occurs:

async public static Task SerializeNiners()
{
    file = await folder.GetFileAsync("niners.xml");
    IOutputStream outputStream;

    using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWriteUnsafe))
    {
        outputStream = stream.GetOutputStreamAt(0);
        uint fileSize = (uint)stream.Size;

        using (TextWriter tWriter = new StringWriter())
        {
            serializer.Serialize(tWriter, BindingPoint.Instance.Niners);

            using (DataWriter writer = new DataWriter(outputStream))
            {
                writer.WriteString(tWriter.ToString());
                await writer.StoreAsync();
            }
        }
    }
}

First, there are three additional static instances in the class that you should be aware of:

static XmlSerializer serializer = new XmlSerializer(BindingPoint.Instance.Niners.GetType());
static StorageFolder folder = KnownFolders.DocumentsLibrary;
static StorageFile file;

In Windows 8, the data is read in a different manner compared to Windows Phone. Here, you have to handle input and output streams directly. You might think that it would be similar to StreamReader and StreamWriter, but it works on a much lower level. As I am reading the niners.xml file, I am using ReadWriteUnsafe as the default file access mode. When using ReadWrite, I sometimes got an exception notifying me that the file is currently handled by another process and cannot be accessed. Despite the fact that I did not see any process that could be using it, I was not able to write to it. ReadWriteUnsafe seems to be solving this problem pretty well.

The serialization process occurs in the same way as on Windows Phone - the XmlSerializer instance is working with an ObservableCollection<Niner> that is being obtained from the current list of niners. Only the alias is serialized, so when the application needs to deserialize the list of registered users, it will re-download the content associated with them, including the achievement metadata. This is an implementation I used to avoid data storage overhead when the data itself is dynamic.

The deserialization works in a very similar manner as the serialization, file handling-wise. I am working with input streams instead of output and DataReader instead of DataWriter.

async public static Task DeserializeNiners()
{
    try
    {
        file = await folder.GetFileAsync("niners.xml");
        IInputStream inputStream;

        using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read))
        {
            inputStream = stream.GetInputStreamAt(0);
            uint fileSize = (uint)stream.Size;

            using (DataReader reader = new DataReader(inputStream))
            {
                await reader.LoadAsync(fileSize);
                string textRead = reader.ReadString(fileSize);

                using (TextReader tReader = new StringReader(textRead))
                {
                    var niners = (ObservableCollection<Niner>)serializer.Deserialize(tReader);

                    foreach (Niner niner in niners)
                    {
                        NinerReader nReader = new NinerReader();
                        nReader.GetNiner(niner.Alias, true, extractedNiner =>
                        {
                            BindingPoint.Instance.Niners.Add(extractedNiner);
                        });
                    }
                }
            }
        }
    }
    catch
    {
        Debug.WriteLine("Load file failed.");
    }
}

As the metadata is absent from the deserialized data set, I am using the previously implemented NinerReader class get it.

Where to invoke the serialization and deserialization processes?

It appears that the best place to start the deserialization is when the application launches. Or, for example, when the main page that handles the content frame is loaded. The last option was the preferred one in my case:

async protected override void OnNavigatedTo(NavigationEventArgs e)
{
    App.mainDispatcher = this.Dispatcher;

    RootFrame.Navigate(typeof(HomeView));
    await Util.DataSerializer.DeserializeNiners();
}

Serialization, in my opinion, should occur whenever the collection is modifed. Currently, the only modifier is implemented through the AddUserPage dialog. When the metadata is downloaded, and the niner is added to the tracking set, I am serializing the entire collection, overwriting the existing file:

async private void btnOK_Click(object sender, RoutedEventArgs e)
{
    if (!Util.Helpers.CheckIfUserExists(txtUsername.Text))
    {
        NinerReader reader = new NinerReader();
        reader.GetNiner(txtUsername.Text, true, async niner =>
        {
            BindingPoint.Instance.Niners.Add(niner);
            await Util.DataSerializer.SerializeNiners();
            if (Frame.CanGoBack)
                Frame.GoBack();
        });
    }
    else
    {
        MessageDialog dialog = new MessageDialog("User already registered.");
        await dialog.ShowAsync();
    }
}

Notice that the lambda expression is decorated with async, so that I can invoke an awaitable method (SerializeNiners).

Want to download the very raw (experimental) version of this app?

Get it here.