Part 22 - Storing and Retrieving Serialized Data

Sign in to queue

Description

 Download the source code for this lesson at http://absolutebeginner.codeplex.com/

I want to continue to build on the knowledge we’ve already gained from building the I Love Cupcakes app.  I want to focus on some slightly more advanced ideas so that we can create an even more advanced app.

In this lesson we’ll discuss the technique required to save a text file to the Phone’s storage, then open and read that file from the Phone’s storage.  There are probably several ways to write and read data from a phone.  For example, if we were working with binary data — like an image file — we would go about this differently.  However, for our purposes, we want to merely save application data.

In our case, we will have an object graph that we want to save to the Phone’s storage.  This will be input the user created when working with our app.  If it were a contact management app, it would store the names, addresses, phone numbers and email addresses for our friends and acquaintances.  We will want to store that data on the phone so that we can retrieve it the next time the user wants to look up a contact.  So, we need to figure out how to take an object graph and persist it to storage. 

To do this, we will need to serialize the data.  Serialization is the process of representing an object graph in memory into a stream that can be written to disk using some format that keeps each instance of an object in the object graph distinct.  It keeps all of its state — its property values — as well as it’s relationship to other objects in the object graph intact.

The content of the stream is based on which class in the Phone API you choose to perform the serialization.  In this lesson I’ll look at the DataContractSerializer which will convert our object graph into XML, and I’ll look at the DataContractJsonSerializer which will convert our object graph into Json.  These classes also can DE-SERIALIZE, which performs the opposite task of taking a stream and turning it back into an object graph for use in our app.

Once we have a serialized stream of objects, we can persist it to storage.  On the Phone, each app has a designated area where data for the app should be written and read from called the Local Folder.  The local folder is the root folder of your app’s data store.  Use this folder to persist data on the phone.  In addition to general data storage, there are sub folders that should be used for specific scenarios. 

https://msdn.microsoft.com/en-us/library/windowsphone/develop/ff402541(v=vs.105).aspx

I found this Quickstart very helpful when I was first trying to understand how the Phone stores application data and other media.
 
The way you access the Local Folder is through the:

Windows.Storage.ApplicationData.Current.LocalFolder object

… like so:

ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(fileName);

… and …

ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(fileName, CreationCollisionOption.ReplaceExisting)

These will open a stream that will read contents from the file or write the contents of a stream to a file, respectively.  As you can see, when opening a stream for writing, you have some options regarding file name collisions.  CreationCollisionOption enum has the following values:

OpenIfExists - Instead of writing, open the file into the current stream.

FailIfExists - Throw and exception if a file by the same name already exists.

GenerateUniqueName - Generate a new name for the file if a file by the same name already exists.

ReplaceExisting - Overwrite the existing file with the same name no matter what.

Ok, so there are two basic components.  You need a stream to read a file into, or a stream with data to write to a file.  The stream is the in memory representation of the file.

Then you have the Phone API methods that operate on the physical storage of the Phone.

But there’s also one final (optional) part … the  Phone API classes and methods that perform serialization and deserialization.  They write the object graph to the stream using a particular serialization scheme - XML, Json, Binary, etc.  AND they perform the corollary operation of deserializing the stream from XML, Json, Binary, etc. BACK into an object graph.

To demonstrate these pieces, we’ll create a simple example using the Blank App Template called “StoringRetrievingSerlizing”.

We’ll start by building the User Interface:

    <StackPanel Margin="0,50,0,0">
        <Button x:Name="writeButton"
                Content="Write"
                Click="writeButton_Click"
                />
       
        <Button x:Name="readButton"
                Content="Read"
                Click="readButton_Click" />
       
        <TextBlock x:Name="resultTextBlock"
                   FontSize="24"
                   Height="400"
                   TextWrapping="Wrap"
                   />
       
    </StackPanel>

Next, I’ll create an object graph.  I’ll define a class (Car, for simplicity’s sake) and a helper method that will create instances of the Car class and save them in a List<Car>:

    public class Car
    {
        public int Id { get; set; }
        public string Make { get; set; }
        public string Model { get; set; }
        public int Year { get; set; }
    }

… and …

private List<Car> buildObjectGraph()
{
  var myCars = new List<Car>();

  myCars.Add(new Car() { Id = 1, Make = "Oldsmobile", Model = "Cutlas Supreme", Year = 1985 });
  myCars.Add(new Car() { Id = 2, Make = "Geo", Model = "Prism", Year = 1993 });
  myCars.Add(new Car() { Id = 3, Make = "Ford", Model = "Escape", Year = 2005 });

   return myCars;
}

In the first example, the Write button will serialize the object graph to XML, then save it as a file to the Local Folder.  The Read button will read the file from the Local Folder and simply display the contents to screen.  We will NOT deserialize the XML back to an object graph in this example because I want you to see the XML that was created.  Therefore, we’ll simply use a StreamReader class to read the content from the stream into a String that we can then display in the resultTextBlock element.

private const string XMLFILENAME = "data.xml";

private async Task writeXMLAsync()
{
  var myCars = buildObjectGraph();

  var serializer = new DataContractSerializer(typeof(List<Car>));
  using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(
                    XMLFILENAME,
                    CreationCollisionOption.ReplaceExisting))
  {
    serializer.WriteObject(stream, myCars);
  }

  resultTextBlock.Text = "Write succeeded";
}

private async Task readXMLAsync()
{
  string content = String.Empty;

  var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(XMLFILENAME);
  using (StreamReader reader = new StreamReader(myStream))
  {
    content = await reader.ReadToEndAsync();
  }

  resultTextBlock.Text = content;
}

… Now, we’ll call these two helper methods from the appropriate button click event handler methods:

private async void readButton_Click(object sender, RoutedEventArgs e)
{
  await readXMLAsync();
}

private async void writeButton_Click(object sender, RoutedEventArgs e)
{
  await writeXMLAsync();
}

Run the app, click the Write button, then the Read button to see the results:

clip_image002

In this series of lessons we’ll use Json as our serialization format of choice.  I suppose there’s nothing wrong with XML other than it being verbose.  It has gotten a bad rap through the years as being difficult to work with.  There’s a joke: “I had a problem, and to solve it I used XML.  Now I have two problems.” 

Json seems to be the preferred data format de jour.  It’s less verbose, and it is the native format for JavaScript objects, and JavaScript is all the rage right now.  But other than that, I see no technical reason to prefer one over the other.  Nonetheless, we’ll be using Json from here on out.

Let’s create two new helper methods that work with Json instead of XML.  As you’ll see, the code is almost identical:

private const string JSONFILENAME = "data.json";

private async Task writeJsonAsync()
{
  // Notice that the write is ALMOST identical ... except for the serializer.

  var myCars = buildObjectGraph();

  var serializer = new DataContractJsonSerializer(typeof(List<Car>));
  using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(
                JSONFILENAME,
                CreationCollisionOption.ReplaceExisting))
  {
  serializer.WriteObject(stream, myCars);
  }

  resultTextBlock.Text = "Write succeeded";
}

private async Task readJsonAsync()
{
  // Notice that the write **IS** identical ... except for the serializer.

  string content = String.Empty;

  var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(JSONFILENAME);
  using (StreamReader reader = new StreamReader(myStream))
  {
    content = await reader.ReadToEndAsync();
  }

  resultTextBlock.Text = content;
}

… And I’ll modify the button click events …

private async void readButton_Click(object sender, RoutedEventArgs e)
{
  //await readXMLAsync();
  await readJsonAsync();
}

private async void writeButton_Click(object sender, RoutedEventArgs e)
{
  //await writeXMLAsync();
  await writeJsonAsync();
}

When I run the app …

clip_image004

But ultimately we do want to work with an object graph, not just Json.  So, I’ll create one final helper method:

private async Task deserializeJsonAsync()
{
  string content = String.Empty;

  List<Car> myCars;
  var jsonSerializer = new DataContractJsonSerializer(typeof(List<Car>));

  var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(JSONFILENAME);

  myCars = (List<Car>)jsonSerializer.ReadObject(myStream);

  foreach (var car in myCars)
  {
    content += String.Format("ID: {0}, Make: {1}, Model: {2} ... ", car.Id, car.Make, car.Model);
  }

  resultTextBlock.Text = content;
}

And change the Read button’s event handler method to call it:

private async void readButton_Click(object sender, RoutedEventArgs e)
{
  //await readXMLAsync();
  //await readJsonAsync();
  await deserializeJsonAsync();
}

… and then run the app …

clip_image006

Recap

The biggest challenge is understanding the role each of the pieces play.  If you don’t fundamentally understand that there are four parts:

- The object graph
- The serialization classes / methods
- The stream
- The file in the Local Folder

… then it will be a mystery when you ask “What do I need to do next?”  Hopefully you can remember at a high level the notion of serialization because it plays a big role whenever transferring data between layers or formats.  A good example is serializing and deserializing data for transfer via web services.

Embed

Download

Download this episode

The Discussion

  • User profile image
    Daniel

    Isn't there a way to use a relational database to persist data on WP8.1 apps instead of json files? To be honest, it doesn't even need to be relational, just an easier way to make "intelligent queries" to the application data instead of having to manually sweep through all of the stored data... I couldn't find it anywhere on the Windows Runtime Reference.

    Thanks in advance, and a big thanks for the great tutorial. Your series of tutorials is by far the best way to start as a WP developer that I know of.

  • User profile image
    Karan

    I tried following the code in VS2013. When I click on Write after copying the code snippets for Data Contract, the app crashes with :
    "A first chance exception of type 'System.Runtime.Serialization.InvalidDataContractException' occurred in SYSTEM.RUNTIME.SERIALIZATION.NI.DLL
    A first chance exception of type 'System.Runtime.Serialization.InvalidDataContractException' occurred in mscorlib.ni.dll
    A first chance exception of type 'System.Runtime.Serialization.InvalidDataContractException' occurred in mscorlib.ni.dll"

    Not sure why this is.
    Thanks for your amazing tutorials.

  • User profile image
    Karan

    Following this http://stackoverflow.com/questions/8294187/i-dont-know-why-my-class-cannot-be-serialized helped fix the problem, but should it need to be included?

  • User profile image
    ryanthtra

    @Karan: I had this problem, too.  I think that, if you followed his directions on the video for making the Car class and then typed in the properties yourself using Snippets ("prop", Tab, Tab), you may have forgotten to add the "public" access modifier for class Car.  Once I added "public" before "class Car", I had no more problems.  You don't need to use attributes in this case.

  • User profile image
    Starfall

    Thank you @ryanthtra, it worked perfectly! I had the same error as @Karan, but the "public" before the class Car fixed it! ;)

  • User profile image
    HughD

    Thanks @ryanthtra, I had same problem as @Karan but now working fine with public

  • User profile image
    Daniel

    Hello
    I am a beginner and have a question.
    How can I save the contents of a variable (the text, because there text show my html page inside. You would like to call later). As html file. To use as source for webview.

    Many Thanks

Add Your 2 Cents