Accessing Data

Applies to: Windows Phone 7

Published: August 2011

Author: Nick Randolph

WROX Tech Editors for Windows Phone 7 Articles

Wrox Windows Phone 7 Books

This topic contains the following sections.

The first challenge in working with data in your Windows Phone application is accessing the data from a remote server. This can be done in a number of different ways, which revolve around making an HTTP web request to the remote server. Windows Phone OS 7.0 does not support the use of sockets. However Windows Phone OS 7.1 introduces support for both TCP and UDP sockets. Sockets are useful for scenarios that need to stream data to, or from, a Windows Phone application. Because this article focuses on the most common data requirements for Windows Phone applications it will not cover the use of sockets.

The simplest way to access remote data is to make a direct request. For example, if a web server hosts an XML or a text file, you can make a simple GET request to download that file. This can be done using either the WebClient or the HttpWebRequest class.

WebClient Class

The WebClient class wraps the use of the HttpWebRequest class, providing a simple way to upload and download simple data from a web server. It provides the following methods for downloading content:

  • DownloadStringAsync

  • OpenReadAsync

It also provides two methods for uploading content:

  • UploadStringAsync

  • OpenWriteAsync

As with most blocking operations in Silverlight, all the methods on the WebClient class have the Async suffix indicating that they are asynchronous methods. In this case, rather than supplying a callback delegate as part of the method call, an event is raised when the asynchronous operation is completed. The events are all named appropriately so that they match the corresponding asynchronous method:

  • DownloadStringCompleted

  • OpenReadCompleted

  • UploadStringCompleted

  • OpenWriteCompleted

To use the WebClient class for either downloading or uploading content, you need to perform the following steps in order:

  1. Create an instance of the WebClient class.

  2. Wire up a handler for each asynchronous method you’ll call.

  3. Call the appropriate method to initiate the corresponding asynchronous operation.

Start with a simple example that downloads an XML file from a remote website, makes changes to the contents, and then saves the modified file back to the remote website. The first step is to download the file, but before you do that, you need to create an instance of the WebClient class.

WebClient wc;

public MainPage(){
    InitializeComponent();
    wc = new WebClient();
}

In this case, a Button has been added to the MainPage, and the following method, DownloadStringClick, has been wired up to the Click event.

private void DownloadStringClick(object sender, RoutedEventArgs e)
{
    wc.DownloadStringAsync(new Uri("http://localhost/SimpleDataWeb/participants.xml"));
}

The DownloadStringClick method calls the DownloadStringAsync method on the WebClient instance, supplying the URI from which to download the XML file. Now you need only to add the code for the wc_DownloadStringCompleted method, which is called when the download completes, and wire it up to the DownloadStringCompleted event on the WebClient instance (done in the MainPage constructor).

public MainPage(){
    InitializeComponent();
    wc = new WebClient();
    wc.DownloadStringCompleted += wc_DownloadStringCompleted;
}

private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
        var xmlString = e.Result;
        //<?xml version="1.0" encoding="utf-8" ?>
        //<Participants>
        //  <Participant Name="Fred" Age="45" />
        //  ...
        //  <Participant Name="Wayne" Age="47" />
        //</Participants>

        // Add unique Id attribute to each participant
        AddUniqueIdToParticipants(xmlString);
        // TODO: Upload changed participants back to server
    }
}

In this event handler, you need to check and ensure that no error was raised during the request. Because all requests are done asynchronously, rather raising the exceptions in the background, which would cause your application to terminate unexpectedly, any exceptions are reported back as part of the completed event handler. Therefore, the first thing you should do is to check the Error property on the EventArgs object. When you are satisfied that no errors have occurred, you can then access the Result property, which in this case contains the downloaded file as a string value (an example of this is shown in the comments, which show a list of Participant elements with attributes Name and Age).

Before uploading the participant XML document to the server, give each participant a unique identifier value by adding an Id attribute.

private string AddUniqueIdToParticipants(string participantsXml)
{
    var xml = XElement.Parse(participantsXml);
    var participants = (from p in xml.Descendants("Participant")
                        select p).ToArray();
    Array.ForEach(participants, p =>
    {
        p.Add(new XAttribute("Id", Guid.NewGuid()));
    });
    var xmlString = xml.ToString();
    return xmlString;
}

You also need to attach a handler for when the upload operation completes. While in most cases this would not return any data, it can be used to determine whether the data was successfully uploaded.

public MainPage()
{
    InitializeComponent();
    wc = new WebClient();
    wc.DownloadStringCompleted += wc_DownloadStringCompleted;
    wc.UploadStringCompleted += wc_UploadStringCompleted;
}

private void wc_UploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
{
    if (e.Error == null)
    {
        MessageBox.Show("File successfully uploaded: " + e.Result);
    }
    else 
    {
        MessageBox.Show("Unable to upload file");
    }
}

The final step is to call the UploadStringAsync method, which invokes the asynchronous upload operation.

        // Add unique Id attribute to each participant
        xmlString = AddUniqueIdToParticipants(xmlString);
        // Upload changed participants back to server
        wc.UploadStringAsync(
  new Uri("http://localhost/SimpleDataWeb/UploadService.svc/FileUpload"),   
  xmlString);

In this case, the URI is used for a WCF Service that has been exposed using the webHttpBinding object. The service itself is UploadService.svc, on which the FileUpload method is invoked. FileUpload has the following method signature:

bool FileUpload(Stream input)

As you can see, the FileUpload method simply accepts a stream of data that can then be processed or saved to the server.

If you are downloading or uploading a large amount of data, it’s good practice to display a progress indicator. In some cases, the size of the data might be unknown, in which case you can set the IsIndeterminate property on the ProgressBar control to true. This displays a looping progress bar that indicates when something is occurring in the background. In the case where you know the size of the data, it is preferable to progressively update the actual position of the progress bar. To do this, attach a handler to the DownloadProgressChanged event. This event is raised periodically while the data is being downloaded, allowing you to update the Value property of a ProgressBar object.

public MainPage()
{
    InitializeComponent();
    wc = new WebClient();
    wc.DownloadStringCompleted += wc_DownloadStringCompleted;
    wc.UploadStringCompleted += wc_UploadStringCompleted;
    wc.DownloadProgressChanged += wc_DownloadProgressChanged;
}

void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
    this.DownloadProgress.Value=(e.BytesReceived /e.TotalBytesToReceive) *100;
}

DownloadProgress is an instance of the ProgressBar control with the Minimum property set to 0 and Maximum set to 100. An alternative would be to set the Maximum property equal to e.TotalBytesToReceive and the Value property to e.BytesReceived. Both settings yield the same progress indicator to the user. In Windows Phone OS 7.1, rather than adding a ProgressBar control to your page, you can make use of the built in progress bar contained within the SystemTray. The SystemTray in Windows Phone OS 7.1 has been significantly upgraded allowing you to set the background color to make it appear as part of your application. Where possible you should display the SystemTray within your application and make use of its progress bar by accessing the SystemTray.ProgressIndicator static property.

One of the most important features of the WebClient control is its simplicity. In all of the code that you’ve seen so far, you have not had to worry about what thread you are executing on. You should be aware that all user interaction and subsequent event processing is done on what’s referred to as the UI thread. Executing a lot of code on this thread can cause the user interface to become unresponsive because events raised by the user interacting with the screen are blocked from executing while other code is running. Therefore, for heavy-lifting code or for code that is going to take time to execute, it is advisable to spawn a background thread on which to carry out this work.

In this case, you are interacting with content from a remote server. Because this can take a finite amount of time to retrieve or submit data, this operation should be done on a background thread. In designing Silverlight, it was decided that all blocking requests, such as retrieving data from a remote server, should be carried out asynchronously. When you call DownloadStringAsync, you are invoking an asynchronous operation that does not block the current thread from continuing to execute. Instead, when the operation completes, it raises the completed event, DownloadStringCompleted.

What is important to note about the way that WebClient raises events is that they are all raised on the UI thread. Rather than you having to worry about whether or not the event handler is executed on the UI thread, the WebClient instance handles this complexity for you, jumping back onto the UI thread. This makes it easy for you to update the ProgressBar object on the DownloadProgressChanged event or to display a MessageBox object on the UploadStringCompleted event.

However, this is a double-edged sword. If your application has to do some sophisticated data manipulation or parsing of the data that has just been downloaded, you run the risk of blocking the UI thread. In this case, you have two options: either spawn an additional thread to process the data or use the HttpWebRequest class instead of the WebClient class.

An important change was made in Windows Phone OS 7.1 which changes the behavior of events raised by the WebClient class. In Windows Phone OS 7.0 all events on the WebClient class were raised on the UI thread, regardless of what thread the WebClient request was made on. In Windows Phone OS 7.1 this was fixed so that events are raised on the thread that the request originated on. This would not have changed the preceding examples as these requests all originated from the UI thread since the user invoked the request by clicking on a button. However, if the code had spawned a background thread and invoked a request from that thread, the corresponding events would also be raised on that background thread. In this case you would have to switch back to the UI thread in order to update any UI elements (see the following discussion on the HttpWebRequest for an example of using Dispatcher.BeginInvoke to do this).

HttpWebRequest Class

In Windows Phone OS 7.0 (in the absence of the sockets introduced in Windows Phone OS 7.1), the HttpWebRequest class represents the lowest-level network operation you can perform. Both the WebClient class and the WCF proxy classes all make use of the HttpWebRequest to make HTTP web requests.

Unlike the WebClient class, which seeks to abstract some of the complexities away from working with downloading and uploading data from a remote server, HttpWebRequest gives you the ability to precisely define the request to be made, intercept the request stream to send data, and intercept the response stream to process any received data. Unlike the WebClient class, it does not raise events on the UI thread, which can lead to some exceptions if you do not take this into consideration. In fact, HttpWebRequest does not use events at all. Instead, you supply a delegate that is invoked when the operation has completed.

The following code shows how to create a new HttpWebRequest instance by calling the static CreateHttp method. Creating the request object does not actually invoke the request. The request is not made until you invoke the BeginGetResponse method. This asynchronous method takes a delegate—in this case, the WebRequestCallback method—to be invoked when the response is available:

private HttpWebRequest sharedRequest;
private void DownloadHttpStringClick(object sender, RoutedEventArgs e)
{
    sharedRequest = WebRequest.CreateHttp(new Uri("http://localhost/SimpleDataWeb/participants.xml"));
    sharedRequest.BeginGetResponse(WebRequestCallback, null);
}

private void WebRequestCallback(IAsyncResult ar)
{
    using (var response = sharedRequest.EndGetResponse(ar))
    using(var strm = response.GetResponseStream())
    using(var reader = new StreamReader(strm))
    {
        var xmlString = reader.ReadToEnd();
        this.Dispatcher.BeginInvoke(() => {
                                            this.XmlText.Text = xmlString;
                                        });
    }
}

In the WebRequestCallback method, the first step is to call EndGetResponse, which returns a response object. The response object provides information about the response to the web request. However, if an error occurs during the request (for example a 404 error due to the wrong address), an exception is raised, so it’s important to wrap all code handling the response to an HttpWebRequest object in a try-catch block.

As discussed earlier, one of the biggest differences between the WebClient class and the HttpWebRequest class is that the HttpWebRequest response returns on a background thread. In this example, after the XML has been read out of the response and assigned to the local variable xmlString, it is applied to the Text property of the XmlTextTextBlock control. This step affects the visual layout of this control and raises an exception if done on a background thread. To perform this operation on the UI thread, it is wrapped in a call to Dispatcher.BeginInvoke. This method accepts an Action delegate and is particularly useful for ensuring that any operations that update the UI are done on the correct thread.

Another interesting point to observe about this code is that the newly created request object is assigned to the class variable, sharedRequest. If this code is executed multiple times, this variable is overwritten by every subsequent request, which can lead to issues when extracting the response in the WebRequestCallback method. An alternative is to include the request object itself as the second to the BeginGetResponse method. This parameter is reserved for a state object that is attached to the request and available within the callback method via the AsyncState property on the IAsyncResult object, shown in the following code:

private void DownloadHttpStringClick(object sender, RoutedEventArgs e) {
    var request= WebRequest.CreateHttp(new Uri("http://localhost/SimpleDataWeb/participants.xml"));
    request.BeginGetResponse(WebRequestCallback, request);
}

private void WebRequestCallback(IAsyncResult ar) {
    var request = ar.AsyncState as HttpWebRequest;
    var response = request.EndGetResponse(ar);
    …
}

Working with the HttpWebRequest object is more complex than doing the equivalent task with the WebClient object, but this is useful if you want to ensure that your service requests do not affect the performance of the application by accidentally blocking the UI thread. Uploading data with the HttpWebRequest is also more complex. Rather than simply specifying the data that you want to be uploaded, you must access the request stream itself and write the data. The advantage is that you have much more control over the exact formatting of each request.

xmlString = AddUniqueIdToParticipants(xmlString);
var uploadRequest = WebRequest.CreateHttp(
       new Uri("http://localhost/SimpleDataWeb/UploadService.svc/FileUpload"));
uploadRequest.Method = "POST";
uploadRequest.BeginGetRequestStream((async)=> {
            using (var upStrm = uploadRequest.EndGetRequestStream(async))
            using(var writer = new StreamWriter(upStrm)) {
                    writer.Write(xmlString);
            }
            uploadRequest.BeginGetResponse(UploadWebRequestCallback,uploadRequest);
            }, null);

Before you can write to the request stream, you need to set the Method property to “POST”. The default is “GET”, which is used to retrieve information from a remote server without supplying any request body. Like BeginGetResponse, the BeginGetRequestStream method is an asynchronous method that accepts a callback delegate that is invoked when the request connection has been established. In this case, the supplied delegate is an anonymous method that uses a StreamWriter object to write the updated XML string to the request stream. When done, BeginGetResponse is called to await the confirmation from the server.

Both the WebClient class and the HttpWebRequest class are useful for working with raw HTTP services that use basic verbs such as GET, POST, PUT and DELETE. However, in some cases, you’ll want to work with more complex services that use SOAP to wrap the request and response objects. Rather than having to encode and decode the SOAP envelope, you can simply reference the service from within your application. This generates the appropriate proxy classes that assist you in communicating with those services. For Windows Phone, support for web services is limited to ASMX and WCF services that are configured to expose a basicHttpBinding instance.

The first step in working with a web service is to get Visual Studio 2010 to generate the proxy classes. This is done by right-clicking the project within Solution Explorer and selecting Add Service Reference (Figure 1). In this case, the service we’re referencing is in the same solution, so clicking the Discover button reveals the service information. Alternatively, enter the URL into the Address box and click Go. The service information appears in the Services list.

Figure 1: Add Service Reference

Referenced Image

Select the service that you want to add a reference to, specify the namespace, and click OK. This automatically generates the appropriate proxy classes within your project, as shown in Figure 2.

Figure 2: Service Reference Proxy Classes

Referenced image

To call this service, create an instance of the proxy class—in this case, the MathServiceClient class—and call the appropriate method. You need to be aware that, like most blocking operations in Silverlight, service requests are asynchronous. As with the WebClient object, to be notified when the request has completed and to retrieve the returned data, you need to attach an event handler to the completed event. When Visual Studio generates the service proxy class, it generates both an asynchronous method, in the form [MethodName]Async, and a completed event, in the form [MethodName]Completed. The following code illustrates calling the PI method (which returns pi to the requested number of decimal places) on the MathService object. It is important to attach the event handler prior to calling the asynchronous method to ensure that the response callback cannot be missed.

private void CallWCFServiceClick(object sender, RoutedEventArgs e) {
    var client = new Services.MathServiceClient();
    client.PICompleted += client_PICompleted;
    client.PIAsync(10);
}

void client_PICompleted(object sender, Services.PICompletedEventArgs e){
    PiText.Text = e.Result.ToString();
}

This example simply extracts the Result value of the request and assigns it to the Text property of PiText, a TextBlock added to the MainPage to display the value of PI. Like the WebClient object, the callback is already on the UI thread, which means you can update visual elements without having to call BeginInvoke. It’s important to remember this, particularly if you want to do heavy data processing on the data that is returned. Any work carried out directly in this callback method can potentially cause the UI thread to block. The following code illustrates how you can process the response from the service request on a background thread and then transition back to the UI thread to update any visuals when the processing is complete:

void client_PICompleted(object sender, Services.PICompletedEventArgs e) {
        ThreadPool.QueueUserWorkItem(
                async => {
                        var response = async as Services.PICompletedEventArgs;
                        // Do processing work...

                       this.Dispatcher.BeginInvoke(
                               (Action<object>)(result => {
                                       // Update user interface.
                                       PiText.Text = result.ToString();
                                       })
                               ,response.Result);
                        }
                ,e);    
}

The first step is to queue an operation to be done on a background thread. This is done by calling the QueueUserWorkItem method specifying both the Action delegate to be invoked, in this case an anonymous method, and the state object, which in this case is the service response, e. Within the anonymous method, the state object appears as the untyped async object, so it needs to be cast back to the service response, PICompletedEventArgs. The data processing can then be carried out with the knowledge that it will not block the UI thread. When this is done, a call to BeginInvoke is used to jump back onto the UI thread to update PiText.

There has been a growing trend away from SOAP-based web services to REST-based APIs. Most of these rely on basic HTTP verbs such as POST, GET, PUT and DELETE, which are the web equivalent of Create, Read, Update and Delete (also known as “CRUD,” for database actions). OData is a relatively new web protocol that can be used for publishing data. It can leverage either or both the Atom Publishing Protocol (AtomPub) and JSON to expose data via a REST API.

Create Proxy Classes

The .NET Framework version 4 featured the release of WCF Data Services, which is a Microsoft implementation of OData that can readily be used to expose your data. Depending on what format you request, you can receive data in either Atom or JSON format, the latter of which can provide some optimizations when working over potentially intermittent networks.

The components and tools required to work with an OData source do not come with the Windows Phone OS 7.0 SDK (see the end of this section for using OData with Windows Phone OS 7.1). However, they are easily downloadable from the Codeplex website at http://odata.codeplex.com by clicking the ODataClient_BinariesAndCodeGenToolForWinPhone.zip link. After downloading this zip library, make sure you unblock the file by right-clicking it, selecting Properties, and clicking the Unblock button.

Rather than spending time configuring your own WCF Data Service, connect to a hosted implementation of the Microsoft sample database, Northwind, which can be browsed at http://services.odata.org/Northwind/Northwind.svc. If you navigate to this URL in your web browser, you should be able to see the set of data collections that have been published, such as Categories and Customers, as shown in Figure 3.

Figure 3: Northwind Entities

Referenced Image

Because this format is AtomPub, each element has a href attribute that gives you the relative URL to follow for more information. For example, to get a list of the customers, you can extend the URL to http://services.odata.org/Northwind/Northwind.svc/Customers. Figure 4 shows the detailed customer information retrieved when you browse to this new URL. Notice that the customer has a href attribute in the first link element, which can be used to PUT updates or even DELETE this customer. Other available links such as Customers (‘ALFKI’)/Orders can be used to retrieve additional information about the customers, which, in this case, is following the relationship between the customer and the orders they’ve placed.

Figure 4: Customer Details

Referenced Image

To consume this data with your Windows Phone application, the first step is to generate the necessary proxy and data classes that can be used to communicate with this OData service. In the download from Codeplex, you should see an executable, DataSvcUtil.exe. Copy this executable, along with the two DLLs, System.Data.Services.Client.dll and System.Data.Services.Design.dll, into the project folder for your application. Next, open a command prompt and navigate to the project folder for your application. Run the following command, which connects to the Northwind OData sample and creates the appropriate proxy classes:

datasvcutil /uri:http://services.odata.org/Northwind/Northwind.svc/ /out:.\NorthwindModel.cs 
/Version:2.0 /DataServiceCollection

This generates a file called NorthwindModel.cs, which you need to include in your project. When working with the proxy classes, the first thing that you need to do is create a DataServiceContext instance. The DataSvcUtil.exe utility would have created a strongly typed subclass called NorthwindEntities, which you should create an instance of. When you create it, you specify the base service URL. From there, specify the relative URLs of the entities you want to access.

private static readonly Uri serviceUri = 
        new Uri("http://services.odata.org/Northwind/Northwind.svc");

private DataServiceCollection<Customer> Customers;
private NorthwindEntities Context;

public ODataPage(){
    InitializeComponent();
        
    Context = new NorthwindEntities(serviceUri);}

You have also declared a DataServiceCollection object of type Customer, which you are going to populate with customer names retrieved via OData. The DataServiceCollection object is actually a specialized ObservableCollection object, which means you can data bind directly to it. Any items added, removed, or changed within one of these lists are automatically updated in the user interface. To download the Customers collection, you need to create an instance of DataServiceCollection and subsequently call the LoadAsync method on the Customers collection. This takes an Uri parameter that can be either an absolute URL, which needs to include the base service URL, or a URL that is relative to the base service URL as in the following code.

private void LoadCustomersClick(object sender, RoutedEventArgs e) {
    Customers = new DataServiceCollection<Customer>(this.Context);
    Customers.LoadCompleted += this.OnCustomersLoaded;
    Customers.LoadAsync(new Uri("/Customers", UriKind.Relative));
}

void OnCustomersLoaded(object sender, LoadCompletedEventArgs e) {
    DataContext = this.Customers;
}

Because the LoadAsync method is an asynchronous call, attach an event handler so that you can process the results. In this case, the Customers collection is simply being set as the DataContext object for the page. To display the customer names, we’ve added a simple ListBox control that displays the CompanyName field by using the following XAML:

<ListBox x:Name="CustomerList" ItemsSource="{Binding}">
    <ListBox.Resources>
        <DataTemplate x:Key="CustomerItemTemplate">
            <TextBlock TextWrapping="Wrap" Text="{Binding CompanyName}" 
                                 Style="{StaticResource PhoneTextNormalStyle}"/>
        </DataTemplate>
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <StaticResource ResourceKey="CustomerItemTemplate"/>
    </ListBox.ItemTemplate>
</ListBox>

If you go ahead and run this, one of the things you can see is that it appears to download only the first twenty or so customers. This is because server-side paging has been enabled, limiting the number of customers that can be returned in any given request. To get the remaining customers, call the LoadNextPartialSetAsync method.

void OnCustomersLoaded(object sender, LoadCompletedEventArgs e)
{
    if (Customers.Continuation != null) {
        Customers.LoadNextPartialSetAsync();
    }
}

You now have a list of customers, but what about seeing more information about them, such as the list of orders they’ve placed? The proxy classes that were generated from the OData service URL include information about the relationships between entities. So, for example, the Orders property on the Customer class can return the list of orders for the customer. However, by default, when you request a customer or a list of customers, that’s all you get. You do not get any order information unless you request it.

You have two ways to tackle this: The first is to simply request the order information along with the original customer request. The following query uses the $expand notation in the URL to indicate the relationships that should be expanded as part of the request.

"/Customers?$expand=Orders"

This can be extended to include other relationships—for example, from the Customer, continuing through the Orders relationship. For example, the following URL expands the relationship to Orders, through to Order_Details, as well as the relationship from Customers to CustomerDemographics.

“/Customers?$expand=Orders/Order_Details,CustomerDemographics”

The downside to this approach is that you could be downloading a large quantity of data on each request. You’re not only downloading the customer information but also all of their associated orders.

The second option is to download the orders information when a customer is selected from the list. Start by adding an event handler to the CustomersListListBox control for the SelectionChanged event. In this event handler, call the LoadAsync method. You do not need to provide a Uri parameter for this method because the relationship between the selected customer and her orders are used to determine the appropriate URL to call.

private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
    var customer = (sender as ListBox).SelectedItem as Customer;
    if (customer != null) {
        customer.Orders.LoadAsync();
    }
}

Because the Orders property is another DataServiceCollection object, you can simply data bind another ListBox control to this property and it is automatically updated with the corresponding order information after it’s been downloaded.

<ListBox x:Name="OrdersList" 
         DataContext="{Binding SelectedItem, ElementName=CustomersList}"
         ItemsSource="{Binding Orders}">
    <ListBox.Resources>
        <DataTemplate x:Key="OrderItemTemplate">
            <TextBlock TextWrapping="Wrap" Text="{Binding OrderDate}" 
                                 Style="{StaticResource PhoneTextNormalStyle}"/>
        </DataTemplate>
    </ListBox.Resources>
    <ListBox.ItemTemplate>
        <StaticResource ResourceKey="OrderItemTemplate"/>
    </ListBox.ItemTemplate>
</ListBox>

This XAML links the OrdersList object to the SelectedItem object of the CustomersList element and displays the OrderDate value for each of the orders for that customer.

Query Data Using Uri Query String Parameters

In the previous section, you saw that by using the $expand operator you can traverse entity relationships. The OData syntax also supports a rich set of query parameters that can be used to filter, sort, and project data. Take a look at each of these in turn.

The $orderby operator can be used to sort ascending (default) or descending:

“/Customers?$orderby=CompanyName” or “/Customers?$orderby=CompanyName asc”
“/Customers?$orderby=CompanyName”

Instead of relying on server batching, it is good practice to use client-side batching, using a combination of the $top and $skip operators. The $top operator retrieves the first n number of items, where n is the number value assigned to the $top operator. On the other hand, $skip jumps over n number of items. You can use these two in conjunction to progressively download the entire customer collection.

“/Customers?$top=5”Selects the first 5 customers

“/Customers?$skip=5&$top=5”Skips the first 5, and selects the next 5 customers

“/Customers?$skip=10&$top=5”Skips the first 10, and selects the next 5 customers.

Rather than downloading the full customer list, you might want to use the $filter operator to download only those customers in London:

“/Customers?$filter=City eq 'London'”

This is a simple example of filtering. Many other operators are supported, both arithmetic and logical, which you can combine to build complex filtering expressions.

The process of selecting a subset of the properties of an entity is known as taking a projection. OData, like most database engines, uses the $select operator to do this. The following example selects only the CompanyName property from the Customers list.

"/Customers?$select=CompanyName"

This can be particularly useful if you are looking to minimize the amount of data being downloaded.

Using JSON vs. XML Feeds

As you look at the XML that is returned by the queries described in the preceding section, notice that it’s verbose and carries a large amount of overhead for such a small amount of data. The alternative is to request the data in JSON, rather than in the AtomPub format we’ve been working with so far. Unfortunately, you cannot easily use the proxy-generated classes with the JSON format. However, if your application needs to synchronize a significant amount of data, it is worth considering. For example, in the following table, the JSON format returns approximately 49 percent of the equivalent Atom request for a simple Customers entity set.

/Customers

28735

14177

49.3%

/Customers?$expand=Orders/Order_Details

817849

277255

33.9%

As the query returns more data, the reduction offered by JSON grows. In the second example, where Orders and Order_Details are expanded, the JSON format returns less than 34 percent as much data as the equivalent Atom. You can see why this is so by taking a quick look at the following chunk of JSON. Information about properties and their types is communicated with minimal overhead:

"{“d” : {“results”: [{“__metadata”: {“uri”: “http://services.odata.org/northwind/Northwind.svc/Customers('ALFKI')”, “type”: “NorthwindModel.Customer”}, “CustomerID”: “ALFKI”, “CompanyName”: “Alfreds Futterkiste”, “ContactName”: “Maria Anders”, “ContactTitle”: “Sales Representative”, “Address”: “Obere Str. 57”, “City”: “Berlin”, “Region”: null, “PostalCode”: “12209”, “Country”: “Germany”, “Phone”: “030-0074321”, “Fax”: “030-0076545”, “Orders”: {“__deferred”: {“uri”: ...................

Unfortunately the proxy classes that were generated previously using the DataSvcUtil.exe utility are not compatible with the JSON format. However, you know the structure that will be returned from the server, so you can use that knowledge to create a set of classes that directly map to the returned structure. This code uses the DataContract and DataMember attributes to identify which classes and which properties should be decoded from the JSON format. These attributes can also override the property names that the serializer looks for in JSON. As shown in the following example, the CustomerData property is mapped to a key of just “d”, which given its descriptive nature, has been optimized already:

[DataContract]
public class JsonCustomersResponse {
    [DataMember(Name="d")]
    public JsonCustomerData CustomerData { get; set; }
}
        
[DataContract]
public class JsonCustomerData {
    [DataMember(Name = "results")]
    public JsonCustomer[] Customers { get; set; }
}

[DataContract]
public class JsonCustomer {
    [DataMember]
    public string CustomerID { get; set; }
    [DataMember]
    public string CompanyName { get; set; }
    [DataMember]
    public string ContactName { get; set; }
}

With these classes in tow, all you need to do is make the appropriate web request and then decode the response. In this case, go with the simplest approach of using the WebClient object:

private void LoadCustomersClick(object sender, RoutedEventArgs e){
    var wc = new WebClient();
    wc.Headers["Accept"] = "application/json";
    wc.OpenReadAsync(new Uri("http://services.odata.org/Northwind/Northwind.svc/Customers"));
}
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e){
    var serializer = new DataContractJsonSerializer(typeof(JsonCustomersResponse));
    var customerData = serializer.ReadObject(e.Result);
}

You’ll notice that the URL being requested is the same as before. However, by setting the “Accept” header to “application/json” the service will return the data in JSON format.

OData in Windows Phone OS 7.1

Windows Phone OS 7.1 comes with built-in support for consuming and working with OData. Referencing an OData source is as easy as adding a reference to the URL of the service. To add an OData source, right-click on the project node in the Solution Explorer window and select Add Service Reference. Enter the URL of the service in the Address field (see Figure 5) and click the Go button. Once the service has been discovered enter a Namespace, in this case Services, and click the OK button to generate the proxy classes.

Figure 5: Add Service Reference

Referenced Image

The proxy classes that you have just generated are similar to what would have been created had you used the DataSvcUtil executable discussed earlier. However, the difference is that in Windows Phone OS 7.1 you can use LINQ, rather than having to manually craft the query URL, to select, filter and sort the data you’re accessing.

Earlier you saw how to retrieve a collection of customers by specifying the “/Customers” relative URL to the LoadAsync method. This can be replaced by using a LINQ query to define which customers to return.

private void LoadCustomersClick(object sender, RoutedEventArgs e) {
    Customers = new DataServiceCollection<Customer>(this.Context);
    var query = from c in Context.Customers
                select c;
    Customers.LoadCompleted += this.OnCustomersLoaded;
    Customers.LoadAsync(query);
}

Using a LINQ expression eliminates the string literal, which reduces the risk of errors as the code is all strongly typed. The LINQ expression also makes it easier to filter, sort, and select data to return. For example the following expression only selects customers starting with ‘A’ and orders them by their Region.

var query = from c in Context.Customers
            where c.CompanyName.StartsWith("A")
            orderby c.Region
            select c;

The OData library for Windows Phone OS 7.1 also includes the DataServiceState class, a helper class for working with the application lifecycle model of Windows Phone. As you are no doubt aware, when your application goes into the background it initially transitions into a dormant state, where it is still memory resident but no longer executing code. From this state your application may either be resumed by the user switching or navigating back to the application, or it may be terminated to free up memory. A terminated application, also known as a tombstoned application, may be restored at a later stage, again by the user either switching or navigating back to the application. To account for both these scenarios any transient data used by the application should be saved to a State dictionary when the application loses focus. The contents of the State dictionary will automatically be persisted and restored as required when the application is terminated and restored.

There are actually multiple State dictionaries into which you can add data, depending on how the data is used throughout the application. For data that is used by multiple pages within the application you should intercept the Deactivated and Activated events on the Application itself (see the App.xaml.cs file which should already exist in your application for these event handlers) and use the PhoneApplicationService.Current.State dictionary. Most data sets will only be relevant to a single page. In this case you should override the OnNavigatedTo and OnNavigatingFrom method and use the State dictionary on the current page. The following example uses the DataServiceState class to serialize the collection of customers you previously retrieved using the OnNavigatingFrom method (i.e. when the user is navigating away from the page). This collection of customers is subsequently restored in the ONavigatedTo method (i.e. when the user navigates back to the page).

protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
    var collections = new Dictionary<string, object>();
    collections.Add("Customers", Customers);

    State["ODataState"] = DataServiceState.Serialize(Context, collections);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if (Customers==null)
    {
        object serializedState;
        DataServiceState transientState;

        if (this.State.TryGetValue("ODataState", out serializedState))
        {
            transientState = DataServiceState.Deserialize(serializedState as string);
            Context = (NorthwindEntities)transientState.Context;
            Customers = transientState.RootCollections["Customers"] as DataServiceCollection<Customer>;
            this.DataContext = Customers;
        }
    }
}

The OnNavigatedTo method has been optimized to ensure it only attempts to reload data from the State dictionary if the application has been tombstoned. If the application had been tombstoned, when it is subsequently restored all page level variables will have their default, or null, value. By checking for this case you are ensuring the application can be restored quickly if it was only in the dormant state. Changes introduced with Windows Phone OS 7.1 means that it is more likely for your application to be restored from the dormant state, so it is important that you appropriately cater for this scenario.

Another important point to note is that the State dictionaries can only hold a limited amount of data. The more data you add to the dictionaries the longer it takes for the dictionaries to be persisted. You should avoid adding large sets of data to the State dictionaries, instead using one of the persistent data storage options, such as isolated storage or LINQ to SQL.

Reducing Network Traffic with Compression

If your OData service is being hosted on IIS 7 you have the option of enabling dynamic compression. This means that your application could request the use of compression as part of each data request by specifying the Accept-Encoding heading. Unfortunately in Windows Phone OS 7.0 setting this header is not permitted. This can be worked around by specifying a different non-restricted header and using an IIS rewrite rule to exchange this for the Accept-Encoding header before the request is processed by IIS. Windows Phone OS 7.1 removes this restriction, allowing your application to set the Accept-Encoding header.

Unfortunately the OData library does not understand how to decompress the data, nor does it permit you to intercept the raw data set coming from the server. As such, if you wish to take advantage of compression you will need to use HttpWebRequest calls to retrieve data, as illustrated previously. If you go down this path you might consider using both JSON and compression to optimize the data being received. However, you should be mindful of any computation and performance overhead introduced by having to decompress the data within the Windows Phone application.

There is no compression library within the Windows Phone SDK but there are open-source implementations that you may consider using within your application such as the Windows Phone port of SharpZipLib. In using third party libraries make sure you respect any licensing that accompanies the library.

Previous Article: Windows Phone 7 Data

Continue on to the Next Article: Storing Data with Isolated Storage and LINQ to SQL

Show: