Статьи

Visual Studio Достижения для Windows Phone — Хранение

Предыдущие части, если вы пропустили их:

Поскольку Niners добавляются в приложение, имеет смысл сохранять их, чтобы пользователю приложения не приходилось создавать один и тот же список снова и снова. Для этого мы могли бы пойти двумя путями: либо использовать систему SQL Compact , либо полагаться на простые файлы в изолированном хранилище . Чтобы принять лучшее решение, давайте посмотрим на данные, которые у меня есть и которые можно сохранить.

Я работаю с одной коллекцией, которая содержит несколько экземпляров Niner. С каждым экземпляром Niner связаны различные метаданные пользователя:

It makes sense to save the user alias since it is static and is not changed. All other properties are essentially dynamic and can be loaded on application startup. Since this is the only informational set that is stored, SQL CE looks like an overkill, so an XML file in the Isolated Storage will do the job just fine. 

The next question is whether I should serialize the collection or simply iterate through the names creating individual nodes? Serialization will require much less code and makes everything a bit more efficient — after all, someday we might want to store additional values and iterating through the whole list might cause some compatibility problems.

Let’s start by modifying the Niner class to include the XmlIgnore attribute for public properties that are not used. Here is what it looks like:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;

namespace VisualStudioAchievements
{
    public class Niner : INotifyPropertyChanged
    {
        string _alias;
        public string Alias
        {
            get
            {
                return _alias;
            }
            set
            {
                if (_alias != value)
                {
                    _alias = value;
                    NotifyPropertyChanged("Alias");
                }
            }
        }

        string _name;
        [XmlIgnore]
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    NotifyPropertyChanged("Name");
                }
            }
        }

        Uri _avatar;
        [XmlIgnore]
        public Uri Avatar
        {
            get
            {
                return _avatar;
            }
            set
            {
                if (_avatar != value)
                {
                    _avatar = value;
                    NotifyPropertyChanged("Avatar");
                }
            }
        }

        List<Achievement> _achievements;
        [XmlIgnore]
        public List<Achievement> Achievements
        {
            get
            {
                return _achievements;
            }
            set
            {
                if (_achievements != value)
                {
                    _achievements = value;
                    NotifyPropertyChanged("Achievements");
                }
            }
        }

        string _caption;
        [XmlIgnore]
        public string Caption
        {
            get
            {
                return _caption;
            }
            set
            {
                if (_caption != value)
                {
                    _caption = value;
                    NotifyPropertyChanged("Caption");
                }
            }
        }

        int _points;
        [XmlIgnore]
        public int Points
        {
            get
            {
                return _points;
            }
            set
            {
                if (_points != value)
                {
                    _points = value;
                    NotifyPropertyChanged("Points");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

The serialization of collection contents can occur at two events — when the collection is modified or when the application is closed. Performance-wise, it wouldn’t make much sense to update the file every time a new Niner is added. When the application is closed, however, it is the perfect time to store the alias values for future reuse.

To avoid confusion, you should remember that the application itself has two «hiding» event handlers:

  • Application_Closing
  • Application_Deactivated

When the application is closes, the OS removes any trace of the application itself in the RAM and all data that was not stored is lost. On deactivation, the application is tombstoned — the process still gets terminated but there is a record of the application stack, so that the system can resume the application’s work once the process that took the foreground is finished.

For more information on application states, I recommend reading Understanding the Windows Phone Application Execution Model, Tombstoning, Launcher and Choosers, and Few More Things That Are on the Way – Part 1

We need to serialize data in both cases. To do this, I created a single method called SerializeNiners in a static class named Util — it will be used for various helper methods along the way.

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

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

When it comes to deserialization, the reverse method applies:

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

    using (var stream = new IsolatedStorageFileStream("niners.xml", FileMode.Open, userStore))
    {
        XmlSerializer serializer = new XmlSerializer(BindingPoint.Niners.GetType());
        var niners = (ObservableCollection<Niner>)serializer.Deserialize(stream);

        NinerReader reader = new NinerReader();

        foreach (Niner niner in niners)
        {
            reader.AddNiner(niner.Alias, true);    
        }
    }
}

Notice that since I am only storing the aliases, I need to re-download dynamic data. I don’t instantly initialize the Niners collection and instead let the NinerReader instance do it for me.

You should add a reference to System.Xml.Serialization DLL in order to be able to use the XmlSerializer class. 

In the App class, make sure you add two calls. One in Application_Closing:

// Code to execute when the application is closing (eg, user hit Back)
// This code will not execute when the application is deactivated
private void Application_Closing(object sender, ClosingEventArgs e)
{
    Util.SerializeNiners();
}

And another one in Application_Launching:

// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
    Util.DeserializeNiners();
}

Now you have the Niners stored for continuous reuse. As a quick reminder, you can pull the latest source here.