Jul 092012
 
 July 9, 2012  Posted by at 11:00 pm WinRT  Add comments

EDIT- Please have a look at a newer blog post with a more detailed example, that uses XMLserializer (both ways are included int the code). A few errors have also been fixed Example Metro app /WinRT: Serializing and deseralizing objects using XMLSerializer to StorageFile and LocalFolder using generics and async/await threading

This one was a hard one! I just couldn’t find ANY working examples of how to serialize and deserialize objects to local storage or local file. And it should be so simple, right? Well, here is an example app as always – enjoy 🙂

Serializing and deseralizing objects to local storage/ local file using generics and async/await threading

The sample app can be downloaded here

Problem:
Your app has to work offline as well and you are looking for a way to store smaller amount of data. You would like to serialize objects to a the local storage in a local file.

Some requriements applicable from Application Profile Survey:
Networking: behaves well offline and while on intermittent internet connection…
Performance: rely on local data as much as possible…

The serialize-helperclass
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;
using System.IO;
using System.Runtime.Serialization;
using Windows.Storage.Streams;

namespace SerializeListWinRT.DataModel
{
class LocalStorage
{
private static List<object> _data = new List<object>();

public static List<object> Data
{
get { return _data; }
set { _data = value; }
}

private const string filename = "cats.xml";

static async public Task Save<T>()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.SaveAsync<T>().Wait(), Windows.System.Threading.WorkItemPriority.Normal);
}

static async public Task Restore<T>()
{
await Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.RestoreAsync<T>().Wait(), Windows.System.Threading.WorkItemPriority.Normal);
}

static async private Task SaveAsync<T>()
{
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
IRandomAccessStream sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite);
IOutputStream sessionOutputStream = sessionRandomAccess.GetOutputStreamAt(0);
var sessionSerializer = new DataContractSerializer(typeof(List<object>), new Type[] { typeof(T) });
sessionSerializer.WriteObject(sessionOutputStream.AsStreamForWrite(), _data);
await sessionOutputStream.FlushAsync();
}

static async private Task RestoreAsync<T>()
{
StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists);
if (sessionFile == null)
{
return;
}
IInputStream sessionInputStream = await sessionFile.OpenReadAsync();
var sessionSerializer = new DataContractSerializer(typeof(List<object>), new Type[] { typeof(T) });
_data = (List<object>)sessionSerializer.ReadObject(sessionInputStream.AsStreamForRead());
}
}
}
[/sourcecode]

The class that will be serialized
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace SerializeListWinRT
{
[KnownType(typeof(SerializeListWinRT.Cat))]
[DataContractAttribute]
public class Cat
{
[DataMember()]
public String Name { get; set; }

[DataMember()]
public String About { get; set; }
}
}
[/sourcecode]
The view
[sourcecode language=”XML”]
<Page
x:Class="SerializeListWinRT.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SerializeListWinRT"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}" >
<StackPanel Orientation="Horizontal" >
<ListView x:Name="AllItemsView" Width="200" Margin="40,20,0,0" Height="400" VerticalAlignment="Top" Background="DarkGray" ItemsSource="{Binding Cats}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}"></TextBlock>
<TextBlock TextWrapping="Wrap" Text="{Binding Path=About}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Width="200" Margin="20,50,0,0" Height="450" VerticalAlignment="Top">
<TextBlock>Name</TextBlock>
<TextBox Text="{Binding NewCat.Name, Mode=TwoWay}"></TextBox>
<TextBlock>About</TextBlock>
<TextBox Text="{Binding NewCat.About, Mode=TwoWay}"></TextBox>
<Button VerticalAlignment="Top" Margin="30" Width="130" Click="Button_Click_1">Add</Button>
<Button VerticalAlignment="Top" Margin="30" Width="130" Click="Button_Click_2">Save to file</Button>
</StackPanel>
</StackPanel>
</Grid>
</Page>
[/sourcecode]
The code that does the magic
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using SerializeListWinRT.DataModel;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace SerializeListWinRT
{

public sealed partial class MainPage : Page
{
private readonly ObservableCollection<Cat> _cats = new ObservableCollection<Cat>();

public ObservableCollection<Cat> Cats
{
get { return _cats; }
}

public MainPage()
{
this.InitializeComponent();
ClearLists();
AddCatsToList();
CreateNewCat();
}

private async void AddCatsToList()
{
await LocalStorage.Restore<Cat>();
SetCatList();
}

private void ClearLists()
{
Cats.Clear();
LocalStorage.Data.Clear();
}

private void SetCatList()
{
foreach (var item in LocalStorage.Data)
{
_cats.Add(item as Cat);
}
}

public Cat NewCat { get; set; }

private void CreateNewCat()
{
NewCat = new Cat();
}

private void AddNewCat()
{
_cats.Add(new Cat {Name = NewCat.Name, About = NewCat.About});
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}

private void Button_Click_1(object sender, RoutedEventArgs e)
{
AddNewCat();
}

private void Button_Click_2(object sender, RoutedEventArgs e)
{
AddNewCat();
SaveList();
}

private void SaveList()
{
LocalStorage.Data.Add(NewCat);
LocalStorage.Save<Cat>();
}

}
}
[/sourcecode]

  8 Responses to “Example Metro app /WinRT: Serializing and deseralizing objects to StorageFile and LocalFolder using generics and async/await threading”

  1. Thanks!

  2. I get the following exception thrown when the call in your code to Save() or Restore() is made. Any idea why?

    System.AggregateException was unhandled by user code
    HResult=-2146233088
    Message=One or more errors occurred.
    Source=mscorlib
    StackTrace:
    at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
    at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
    at System.Threading.Tasks.Task.Wait()
    at SerializeListWinRT.DataModel.LocalStorage.b__4[T](IAsyncAction sender) in c:\temp\store\SerializeListWinRT_small\SerializeListWinRT\LocalStorage.cs:line 33
    InnerException: System.Xml.XmlException
    HResult=-2146232000
    Message=Unexpected end of file.
    Source=System.Runtime.Serialization
    LineNumber=0
    LinePosition=0
    StackTrace:
    at System.Xml.EncodingStreamWrapper.ReadBOMEncoding(Boolean notOutOfBand)
    at System.Xml.EncodingStreamWrapper..ctor(Stream stream, Encoding encoding)
    at System.Xml.XmlUTF8TextReader.SetInput(Stream stream, Encoding encoding, XmlDictionaryReaderQuotas quotas, OnXmlDictionaryReaderClose onClose)
    at System.Runtime.Serialization.XmlObjectSerializer.ReadObject(Stream stream)
    at SerializeListWinRT.DataModel.LocalStorage.d__11`1.MoveNext() in c:\temp\store\SerializeListWinRT_small\SerializeListWinRT\LocalStorage.cs:line 55
    InnerException:

  3. I appreciate the code but I guess there is an issue with the RestoreAsync method; I moved this to the roaming storage, but I guess it does not matter;
    You are using CreateFileAsync method to locate the file (open it). This will create a file if there is none and return null to the object serializer which will fail.

    I have used GetFileAsync that throws when file is not found but does not create an empty one. You need to catch the exception though.

    public static async Task ReadRemotelyAsync()
    {
    try
    {
    var sessionFile = await ApplicationData.Current.RoamingFolder.GetFileAsync(AppSettingsFilename);
    var sessionInputStream = await sessionFile.OpenReadAsync();
    var sessionSerializer = new DataContractSerializer(typeof(T));
    return (T)sessionSerializer.ReadObject(sessionInputStream.AsStreamForRead());
    }
    catch (Exception)
    {
    return default(T);
    }
    }

  4. The above code is giving the following warning .Can u pls give solution for this.

    “Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.”

    private void SaveList()
    {
    LocalStorage.Data.Add(NewCat);
    LocalStorage.Save(); //warning here
    }

  5. I posted a related solution on Stack Overflow that you may be interested in checking out (http://stackoverflow.com/questions/12562268/xml-serialization-in-windows-8/17055641#17055641)

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

What is 5 + 14 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)