Example Metro app /WinRT: Serializing and deseralizing objects using XMLSerializer to StorageFile and LocalFolder using generics and async/await threading
After a great comment from a user on MSDN I’ve decided to make another example app to show how to serialixe/deserialize with XMLSerialiser- keeping the code where I use DataContractSerializer so you can see both ways of doing it. Methods are still generic. From performance standpoint XMLSerializer is best, and recommended. DataContractSerializer might be something you recognize from WCF, as it is the default- but you can change this if you want.
Example Metro app /WinRT: Serializing and deseralizing objects using XMLSerializer to StorageFile and LocalFolder using generics and async/await threading
So lets add another requriements applicable from Application Profile Survey:
Performance - all the requirements ;)
I’ve also added three more things in the app:
- I’m now explicitly disposing the stream and file objects,- sorry about forgetting that in the previous example, I find IO in Metro Apps a tiny bit challenging :) A second call to the same objects will fail and access will be denied as the object is still in use if not disposed. So you have to dispose explicitly.
2.When you start the application the file will get created for you. Got some emails from some devs that were unsure how to create the first list to get the app running (I just set the Save on application start the first time, then removed the code)3. I’ve seen a lot question about how to check if a file exists, and the answer I’ve found on MSDN is that you would have to catch an exception to do this (if this has changed, please let me know! - I don’t like this solution). So do be able to do change nr 2 I’ve added code to check wether the file exists or not, and let the file be created if it doesn’t.The app can be downloaded here (and you should be able to run and get the file created uplon launch)The serialize-helperclass[sourcecode language=“csharp”]using System;using System.Collections.Generic;using System.Threading.Tasks;using System.Xml.Serialization;using Windows.Storage;using System.IO;using Windows.Storage.Streams;namespace SerializeListWinRT.DataModel{ class LocalStorage { private static List _data = new List(); public static List Data { get { return _data; } set { _data = value; } } public static StorageFile file { get; set; } private const string filename = "animals.xml"; static async public Task Save() { if (await DoesFileExistAsync(filename)) { await Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.SaveAsync().Wait(), Windows.System.Threading.WorkItemPriority.Normal); } else { file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename,CreationCollisionOption.ReplaceExisting); } } static async public Task Restore() { if (await DoesFileExistAsync(filename)) { await Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.RestoreAsync().Wait(), Windows.System.Threading.WorkItemPriority.Normal); } else { file = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename); } } static async Task DoesFileExistAsync(string fileName) { try { await ApplicationData.Current.LocalFolder.GetFileAsync(fileName); return true; } catch { return false; } } static async private Task SaveAsync() { StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting); IRandomAccessStream sessionRandomAccess = await sessionFile.OpenAsync(FileAccessMode.ReadWrite); IOutputStream sessionOutputStream = sessionRandomAccess.GetOutputStreamAt(0); var serializer = new XmlSerializer(typeof (List), new Type[] {typeof (T)}); //Using DataContractSerializer , look at the cat-class //var sessionSerializer = new DataContractSerializer(typeof(List), new Type[] { typeof(T) }); //sessionSerializer.WriteObject(sessionOutputStream.AsStreamForWrite(), _data); //Using XmlSerializer , look at the Dog-class serializer.Serialize(sessionOutputStream.AsStreamForWrite(), _data); sessionRandomAccess.Dispose(); await sessionOutputStream.FlushAsync(); sessionOutputStream.Dispose(); } static async private Task RestoreAsync() { StorageFile sessionFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(filename, CreationCollisionOption.OpenIfExists); if (sessionFile == null) { return; } IInputStream sessionInputStream = await sessionFile.OpenReadAsync(); //Using DataContractSerializer , look at the cat-class // var sessionSerializer = new DataContractSerializer(typeof(List), new Type[] { typeof(T) }); //_data = (List)sessionSerializer.ReadObject(sessionInputStream.AsStreamForRead()); //Using XmlSerializer , look at the Dog-class var serializer = new XmlSerializer(typeof(List), new Type[] { typeof(T) }); _data = (List) serializer.Deserialize(sessionInputStream.AsStreamForRead()); sessionInputStream.Dispose(); } }}[/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"> <ListView x:Name="AllItemsView" Width="200" Margin="40,20,0,0" Height="400" VerticalAlignment="Top" Background="DarkGray" ItemsSource="{Binding Objects}"> <ListView.ItemTemplate> </ListView.ItemTemplate> Name <TextBox Text="{Binding NewObject.Name, Mode=TwoWay}"> About <TextBox Text="{Binding NewObject.About, Mode=TwoWay}"> Add Save to file [/sourcecode]The classes[sourcecode language=“csharp”]using System;using System.Runtime.Serialization;namespace SerializeListWinRT{ [KnownType(typeof(SerializeListWinRT.Cat))] [DataContractAttribute] public class Cat { [DataMember()] public String Name { get; set; } [DataMember()] public String About { get; set; } } public class Dog { public String Name { get; set; } public String About { get; set; } }}[/sourcecode]
Comments
Hi Lisa, here you can find animals.xml C:\Users\USER_NAME\AppData\Local\Packages\PACKAGE_FAMILY_NAME\LocalState For my case PACKAGE_FAMILY_NAME was 7c0c28aa-7da9-4b7e-992d-d1376d5bda93_xe7737nrh9z36 You will get PACKAGE_FAMILY_NAME in "Packaging" tab of "Package.appxmanifest" file. I hope you will find that :)
This example is awesome. Made my day. Thanks!!!
How can I delete a single item from the XML generated? E.g. I'd like to delete the "Dog something" item.
Thanks for the article. I wanted to use this from within a Windows Runtime Component but can't because I'd have to make the types I'm serializing public. I'm gonna have to use the DataContract serialization instead.
I have looked for a example like this for a while and today I found this thanks for dong this and posting it for people like my self that are pretty green to developing! :-)
I ran into this today thanks for the code. I noticed however that you will get better and more reliable performance under stress if you do not await your saves. There is actually no need to await the saves. Otherwise you get Access Denied if you run two saves in a short time, the second save will usually error. So I changed this line await Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.SaveAsync().Wait(), Windows.System.Threading.WorkItemPriority.Normal); to Windows.System.Threading.ThreadPool.RunAsync((sender) => LocalStorage.SaveAsync().Start(), Windows.System.Threading.WorkItemPriority.Normal); Also note that .Wait() was changed to .Start() Additionally the Save call from the app main was also changed to no await the save call like this LocalStorage.Save();
Thanks for your article, but how to remove items from listview,
Nice article. It will make my day! When app starts a few times the file "animals.xml" exists but is empty and serializer encounters problems ("Message=Root element is missing."). I will solve this using try-catch-finally block. static async private Task RestoreAsync() { //... try { //Using XmlSerializer , look at the Dog-class var serializer = new XmlSerializer(typeof(List), new Type[] { typeof(T) }); _data = (List)serializer.Deserialize(sessionInputStream.AsStreamForRead()); } catch (Exception) { } finally { sessionInputStream.Dispose(); } } Have a nice day.
Thank you again for your nice article. It still made my day! Bind to App.MyDogs your GUI. Save and restore whenever you want. Have a nice day. public class Dog : INotifyPropertyChanged { private string name = string.Empty; public string Name { get { return name; } set { if (name == value) { return; } name = value; RaisePropertyChanged("Name"); } } private string about = string.Empty; public string About { get { return about; } set { if (about == value) { return; } about = value; RaisePropertyChanged("About"); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } App.xaml.cs public static ObservableCollection MyDogs = new ObservableCollection(); Dog dog; dog = new Dog { Name = "Tootsie", About = "small" }; MyDogs.Add(dog); dog = new Dog { Name = "Flexsie", About = "flexibel" }; MyDogs.Add(dog); private const string xmlFileName = "dogs.xml"; Task.Run(() => Save<ObservableCollection>(App.MyDogs, xmlFileName)); private static async void Save(T observableCollection, string xmlFileName) { var xmlSerializer = new XmlSerializer(typeof(T)); // StorageFolder localFolder = ApplicationData.Current.LocalFolder; // StorageFolder localSubFolder = await localFolder.GetFolderAsync("MySubFolder"); StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder; StorageFolder roamingSubFolder = await roamingFolder.CreateFolderAsync("MySubFolder", CreationCollisionOption.OpenIfExists); StorageFile storageFile = null; try { storageFile = await roamingSubFolder.CreateFileAsync(xmlFileName, CreationCollisionOption.ReplaceExisting); } catch (Exception) { } if (storageFile != null) { using (IRandomAccessStream sessionRandomAccess = await storageFile.OpenAsync(FileAccessMode.ReadWrite)) { using (IOutputStream outputStream = sessionRandomAccess.GetOutputStreamAt(0)) { xmlSerializer.Serialize(outputStream.AsStreamForWrite(), observableCollection); await outputStream.FlushAsync(); } } } } Task.Run(() => RestoreMyDogs (App. MyDogs, xmlFileName)); private static async void RestoreMyDogs(ObservableCollection observableCollection, string xmlFileName) { var xmlSerializer = new XmlSerializer(typeof(ObservableCollection)); // StorageFolder localFolder = ApplicationData.Current.LocalFolder; // StorageFolder localSubFolder = await localFolder.GetFolderAsync("MySubFolder"); StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder; StorageFolder roamingSubFolder = await roamingFolder.GetFolderAsync("MySubFolder"); StorageFile storageFile = null; try { storageFile = await roamingSubFolder.GetFileAsync(xmlFileName); } catch (Exception) { } if (storageFile != null) { using (IInputStream inputStream = await storageFile.OpenReadAsync()) { App.MyDogs = (ObservableCollection)xmlSerializer.Deserialize(inputStream.AsStreamForRead()); } } }
Text was no narrow! Once again. public class Dog : INotifyPropertyChanged { private string name = string.Empty; public string Name { get { return name; } set { if (name == value) { return; } name = value; RaisePropertyChanged("Name"); } } private string about = string.Empty; public string About { get { return about; } set { if (about == value) { return; } about = value; RaisePropertyChanged("About"); } } public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } } App.xaml.cs public static ObservableCollection MyDogs = new ObservableCollection(); Dog dog; dog = new Dog { Name = "Tootsie", About = "small" }; MyDogs.Add(dog); dog = new Dog { Name = "Flexsie", About = "flexibel" }; MyDogs.Add(dog); private const string xmlFileName = "dogs.xml"; Task.Run(() => Save<ObservableCollection>(App.MyDogs, xmlFileName)); private static async void Save(T observableCollection, string xmlFileName) { var xmlSerializer = new XmlSerializer(typeof(T)); // StorageFolder localFolder = ApplicationData.Current.LocalFolder; // StorageFolder localSubFolder = await localFolder.GetFolderAsync("MySubFolder"); StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder; StorageFolder roamingSubFolder = await roamingFolder.CreateFolderAsync("MySubFolder", CreationCollisionOption.OpenIfExists); StorageFile storageFile = null; try { storageFile = await roamingSubFolder.CreateFileAsync(xmlFileName, CreationCollisionOption.ReplaceExisting); } catch (Exception) { } if (storageFile != null) { using (IRandomAccessStream sessionRandomAccess = await storageFile.OpenAsync(FileAccessMode.ReadWrite)) { using (IOutputStream outputStream = sessionRandomAccess.GetOutputStreamAt(0)) { xmlSerializer.Serialize(outputStream.AsStreamForWrite(), observableCollection); await outputStream.FlushAsync(); } } } } Task.Run(() => RestoreMyDogs (App. MyDogs, xmlFileName)); private static async void RestoreMyDogs(ObservableCollection observableCollection, string xmlFileName) { var xmlSerializer = new XmlSerializer(typeof(ObservableCollection)); // StorageFolder localFolder = ApplicationData.Current.LocalFolder; // StorageFolder localSubFolder = await localFolder.GetFolderAsync("MySubFolder"); StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder; StorageFolder roamingSubFolder = await roamingFolder.GetFolderAsync("MySubFolder"); StorageFile storageFile = null; try { storageFile = await roamingSubFolder.GetFileAsync(xmlFileName); } catch (Exception) { } if (storageFile != null) { using (IInputStream inputStream = await storageFile.OpenReadAsync()) { App.MyDogs = (ObservableCollection)xmlSerializer.Deserialize(inputStream.AsStreamForRead()); } } }
Missing important piecies of text. Once again. Task.Run(() => RestoreMyDogs (App.MyDogs, xmlFileName)); private static async void RestoreMyDogs(ObservableCollection observableCollection, string xmlFileName) { var xmlSerializer = new XmlSerializer(typeof(ObservableCollection)); // StorageFolder localFolder = ApplicationData.Current.LocalFolder; // StorageFolder localSubFolder = await localFolder.GetFolderAsync("MySubFolder"); StorageFolder roamingFolder = ApplicationData.Current.RoamingFolder; StorageFolder roamingSubFolder = await roamingFolder.GetFolderAsync("MySubFolder"); StorageFile storageFile = null; try { storageFile = await roamingSubFolder.GetFileAsync(xmlFileName); } catch (Exception) { } if (storageFile != null) { using (IInputStream inputStream = await storageFile.OpenReadAsync()) { App.MyDogs = (ObservableCollection)xmlSerializer.Deserialize(inputStream.AsStreamForRead()); } } }
This was not good. Where it stands ObServableCollection it menas of Dog, but "HTML tags are not allowed." Sorry for that.
Thank you
Hi, im using your solution for ser/deserialize. I have problems, when I serialize List (by button "add word to xml"), later when i try open it in second class (by button show whole xml), I cant read file by show whole xml. I think file is block to read/write. Can u help me solve this? thx
Hello, I have a Windows 8.1 app which hold some static pages of html and it will store all the pages into LocalFolder now I am able to get window.external.notification(...), but not able to InvokeScriptAsync()..? I had tried with many things but no luck. in simple scenario it is working perfectly fine if you have in APPX folder then it will work but if you have your static pages into LocalFolder then not. even notification is also not working directly but I have added StreamUriWinRTResolver and it will working.but now I an not able to InvokeScriptAsync(...) .. please guide me where I am wrong.? Thank you, -Jitendra Jadav.
Last modified on 2012-07-11