2.1 Serialization
In general, creating your own files and
directories will help you store the information you need, but inventing
your file formats is usually unnecessary. There are three common methods
for storing information in a structured way using serialization: XML
serialization, JavaScript Object Notation (JSON) serialization, and
isolated storage settings.
XML Serialization
XML is a common approach to formatting data on the phone. .NET provides built-in support for XML serialization using the XmlSerializer
class.
This class enables you to take a graph of managed objects and create an
XML version. Additionally (and crucially), you can read that XML back
into memory to re-create the object graph.
To get started writing an object graph as XML,
you need to know about the class you want to serialize. For the examples
here, assume you have something like a class that holds user
preferences, like so:
public class UserPreferences
{
public DateTime LastAccessed { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool UsePushNotifications { get; set; }
}
Standard XML serialization requires certain
constructors (empty constructors) to work. If you don’t create a
constructor explicitly, the compiler will create an empty constructor
implicitly. Because XML serialization requires an empty constructor, you
can rely on the one the compiler created (as in the previous example),
but if you add an explicit constructor you need to make sure you also
include a constructor without any parameters (that is, an empty
constructor). Now that we have something to serialize, let’s go ahead
and store the data.
First, you must add the System.Xml.Serialization
assembly to your project. Then you can use the XmlSerialization
class (located in the System.Xml.Serialization
namespace) by creating an instance of it (passing in the type you want to serialize):
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
using (var file = store.CreateFile("myfile.xml"))
{
XmlSerializer ser = new XmlSerializer(typeof(UserPreferences));
}
This creates a serializer for this type. The
instance of the class can serialize or deserialize your type (and
associated types) to XML. To serialize into XML, simply call the Serialize
method:
async void saveXmlButton_Click(object sender, RoutedEventArgs e)
{
var prefs = new UserPreferences()
{
FirstName = "Shawn"
};
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync("prefs.xml",
CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenStreamForWriteAsync())
{
XmlSerializer ser = new XmlSerializer(typeof(UserPreferences));
// Serializes the instance into the newly created file
ser.Serialize(stream, prefs);
// Clean up Stream
await stream.FlushAsync();
stream.Close();
}
}
This stores the instance of the UserPreferences
class into the file created in the local folder. To read that file back, you can reverse the process by using the Deserialize
method, like so:
async void loadXmlButton_Click(object sender, RoutedEventArgs e)
{
UserPreferences prefs = null;
var folder = ApplicationData.Current.LocalFolder;
using (var stream =
await folder.OpenStreamForReadAsync("prefs.xml"))
{
XmlSerializer ser = new XmlSerializer(typeof(UserPreferences));
// Re-creates an instance of the UserPreferences
// from the Serialized data
prefs = (UserPreferences)ser.Deserialize(stream);
}
}
By using the Deserialize
method,
you can re-create the saved data and instantiate a new instance of the
data with the serialized data. The serialization class also supports a CanDeserialize
method, so you can test to see whether the stream contains serializable data that is compatible with the type:
async void loadXmlButton_Click(object sender, RoutedEventArgs e)
{
UserPreferences prefs = null;
var folder = ApplicationData.Current.LocalFolder;
using (var stream =
await folder.OpenStreamForReadAsync("prefs.xml"))
{
{
XmlSerializer ser = new XmlSerializer(typeof(UserPreferences));
// Use an XmlReader to allow us to test for serializability
var reader = XmlReader.Create(file);
// Test to see if the reader contains serializable data
if (ser.CanDeserialize(reader))
{
// Re-creates an instance of the UserPreferences
// from the Serialized data
prefs = (UserPreferences)ser.Deserialize(reader);
}
}
}
To test whether the data contains serializable data, you need to create an XmlReader
object that contains the file contents (as a stream) using the XmlReader.Create
method. After you have an XmlReader
, you can use the serializer’s CanDeserialize
method. If you are going to wrap the file in an XmlReader
, you should also send the reader
object into the Deserialize
method (because this is more efficient than the XmlSerializer
creating a new reader internally).
JSON Serialization
XML is the first choice of a lot of
developers, but it probably should not be today. The proliferation of
JSON means that using the same format for local serialization and any
web- or REST-based interaction is commonplace. In addition, JSON tends
to be smaller when serialized than XML. Although size alone isn’t a
reason to choose JSON, smaller generally means faster—and that is a good
reason to pick it.
In place of the XmlSerializer
class, you can use the DataContractJsonSerializer
class. This class is part of the WCF (or Service Model) classes that
are part of the Windows Phone SDK. This class lives in the System.Runtime.Serialization
namespace but is implemented in the System.ServiceModel.Web
assembly. This assembly is not included by default, so you need to add it manually.
To use the DataContractJsonSerializer
class, you can create it by specifying the data type to store, like so:
var prefs = new UserPreferences()
{
FirstName = "Shawn"
};
var folder = ApplicationData.Current.LocalFolder;
var file = await folder.CreateFileAsync("prefs.xml",
CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenStreamForWriteAsync())
{
// Create the Serializer for our preferences class
var serializer = new
DataContractJsonSerializer(typeof(UserPreferences));
// Save the object as JSON
serializer.WriteObject(file, someInstance);
}
You can see here that the DataContractJsonSerializer
class’s WriteObject
method is used to serialize the instance of the UserPreferences
class as JSON. For deserializing the data back into an instance of the UserPreferences
class, you would use the ReadObject
method instead:
UserPreferences someInstance = null;
var folder = ApplicationData.Current.LocalFolder;
using (var stream = await folder.OpenStreamForReadAsync())
{
// Create the Serializer for our preferences class
var serializer = new
DataContractJsonSerializer(typeof(UserPreferences));
// Load the object from JSON
someInstance = (UserPreferences)serializer.ReadObject(file);
}
In this case, the serialization code simply used the DataContactJsonSerializer
class’s ReadObject
method to load a new instance of the options
class by reading the JSON file that was earlier saved to storage.
Isolated Storage Settings
Sometimes it is just easier to let the system
handle serialization for you. That’s the concept behind isolated storage
settings. The IsolatedStorageSettings
class represents
access to a simple dictionary of things to store for the phone. This
type of storage is useful for small pieces of information (for example,
application settings) that you need to store without the work of
building a complete serialization scheme.
To get started, you must get the settings
object for your application through a static accessor on IsolatedStorageSettings
(in the System.IO.IsolatedStorage
namespace) called ApplicationSettings
, like so:
IsolatedStorageSettings settings =
IsolatedStorageSettings.ApplicationSettings;
This class exposes a dictionary of properties
that are automatically serialized to the local folder for you. For
example, if you want to store a setting when closing the application and
read it back when launching the application, you can do this with the IsolatedStorageSettings
class, like so:
Color _favoriteColor = Colors.Blue;
const string COLORKEY = "FavoriteColor";
void Application_Launching(object sender, LaunchingEventArgs e)
{
if (IsolatedStorageSettings.ApplicationSettings.Contains(COLORKEY))
{
_favoriteColor =
(Color)IsolatedStorageSettings.ApplicationSettings[COLORKEY];
}
}
void Application_Closing(object sender, ClosingEventArgs e)
{
IsolatedStorageSettings.ApplicationSettings[COLORKEY] =
_favoriteColor;
}
By using the IsolatedStorageSettings
class’s ApplicationSettings
property, you can set and get simple values. You can see that during
the launch of the application, the code first checks whether the key is
in the ApplicationSettings
(to ensure that it has been saved at least once); if so, it loads the value from the local folder.
The IsolatedStorageSettings
class
does work well with simple types, but as long as the data you want to
store is compatible with XML serialization (like our earlier example),
the settings file will support saving it as well:
UserPreferences _preferences = new UserPreferences();
const string PREFKEY = "USERPREFS";
void Application_Launching(object sender, LaunchingEventArgs e)
{
if (IsolatedStorageSettings.ApplicationSettings.Contains(PREFKEY))
{
_preferences = (UserPreferences)
IsolatedStorageSettings.ApplicationSettings[PREFKEY];
}
}
void Application_Closing(object sender, ClosingEventArgs e)
{
IsolatedStorageSettings.ApplicationSettings[PREFKEY] =
_preferences;
}