Compartir a través de


OData client for Windows Phone 8

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

The Open Data Protocol (OData) is based on an entity and relationship model that enables you to access data in the style of representational state transfer (REST) resources. By using the OData client library for Windows Phone, Windows Phone apps can use the standard HTTP protocol to execute queries, and even to create, update, and delete data from a data service. The OData client for Windows Phone is not part of the Windows Phone SDK, you must download it separately from: WCF Data Services Tools for Visual Studio. The client library generates HTTP requests to any service that supports the OData v3 protocol and transforms the data in the response feed into objects on the client. For more information about OData and existing data services that can be accessed by using the OData client library for Windows Phone, see the OData website.

The two main classes of the client library are DataServiceContext and DataServiceCollection. The DataServiceContext class encapsulates operations that are executed against a specific data service. OData-based services are stateless. However, the DataServiceContext class maintains the state of entities on the client between interactions with the data service. This enables the client to support features such as change tracking and identity management. The OData client library for Windows Phone provides the same functionality to asynchronously access an OData service that is provided in the WCF Data Services client that is included in the .NET Framework.

The following sections in this topic describe how to use the client library to access an OData service:

  • Generating Client Proxy Classes

  • Binding Data to Controls

  • Accessing and Changing Resources

    • Querying Resources

    • LINQ Queries

    • Loading Deferred Content

    • Working with Binary Data

    • Query Projection

  • Modifying Resources and Saving Changes

  • Maintaining State during Application Execution

  • Client Authentication

  • Message Compression

Generating client proxy classes

You can use the Add Service Reference dialog box in Visual Studio to add a reference to any service that exposes an OData feed. For more info, see How to consume an OData service for Windows Phone 8. The client proxy classes can also be generated by using the DataSvcUtil.exe tool at the command prompt.

Binding data to controls

The DataServiceCollection class, which inherits from the ObservableCollection<(Of <(T>)>), represents a dynamic data collection that provides notifications when items get added to or removed from the collection. These notifications enable DataServiceContext to track changes automatically without explicit calls to the change tracking methods.

A query determines which data objects the DataServiceCollection instance will contain. The query can either be composed as a Language Integrated Query (LINQ) query using the DataServiceQuery class, or it can be provided in the form of a uniform resource identifier (URI). For more information about the syntax of URI queries in OData see, OData: URI Conventions. This query is then specified as a parameter in the LoadAsync(IQueryable<(Of <(UTP>)>)) method of the DataServiceCollection class. When executed, this method returns an OData feed that is deserialized into data objects in the collection.

The LoadAsync(IQueryable<(Of <(UTP>)>)) method of the DataServiceCollection class ensures that the results are marshaled to the correct thread, so you do not need to use a Dispatcher object. The LoadCompleted()()() event is raised after a response is received from the data service.

Note

You can cancel an active LoadAsync()()() operation by calling the CancelAsyncLoad()()() method.

When you use an instance of DataServiceCollection for data binding, the client ensures that objects tracked by the DataServiceContext remain synchronized with the data in the bound UI element. You do not need to manually report changes to entities in a binding collection.

Tip

We recommend employing a Model-View-ViewModel (MVVM) design pattern for your data apps where the model is generated based on the model returned by the data service. By using this approach, you can create the DataServiceContext in the ViewModel class and expose any needed DataServiceCollection instances or other data structures required for data binding to Windows Phone controls. For more general info about the MVVM pattern, see Implementing the Model-View-ViewModel pattern for Windows Phone 8.

Accessing and changing resources

In a Windows Phone app, all operations against a data service are asynchronous. You perform asynchronous operations by using pairs of methods on the DataServiceContext class that start with Begin and End respectively. The Begin methods register a delegate that the service calls when the operation is completed. The End methods should be called in the delegate that is registered to handle the callback from the completed operations.

Note

When using the DataServiceCollection class, the asynchronous operations and marshaling are handed automatically. When using asynchronous operations directly, you must use the BeginInvoke method of the Dispatcher class to correctly marshal the response operation back to the main app thread (the UI thread) of your app.

When you call the End method to complete an asynchronous operation, you must do so from the same DataServiceContext instance that was used to begin the operation. Each Begin method takes a parameter that can pass a state object to the callback. This state object is retrieved using the IAsyncResult interface that is supplied with the callback and is used to call the corresponding End method to complete the asynchronous operation.

For example, when you supply the DataServiceContext instance as the state parameter when you call the BeginExecute``1(DataServiceQueryContinuation<(Of <(UMP>)>), AsyncCallback, Object) method on the instance, the same DataServiceContext instance is returned as the IAsyncResult parameter. This instance of the DataServiceContext is then used to call the EndExecute``1(IAsyncResult) method to complete the query operation. For more info, see Asynchronous Operations (WCF Data Services).

Querying resources

The OData client library for Windows Phone enables you to execute queries against an OData service by using familiar programming patterns that include using Language Integrated Query (LINQ). When a DataServiceQuery or query URI is executed, usually when calling the LoadAsync(IQueryable<(Of <(UTP>)>)) method, the client library translates a query or URI into an HTTP GET request message. The client library receives the corresponding response message and translates entries in the returned feed into instances of client data service classes. These classes are tracked by the DataServiceContext to which the DataServiceQuery belongs.

In some scenarios, it is helpful to know the total number of entities in an entity set and not merely the number in the feed returned by the query. Call the IncludeTotalCount()()() method on the DataServiceQuery to request that this total count of entities in the set be included with the query result. In this case, the TotalCount()()() property of the returned QueryOperationResponse returns the total number of entities in the set. The QueryOperationResponse can be obtained from the LoadCompletedEventArgs received when handling the LoadCompleted()()() event.

When you compose a query by using the DataServiceQuery class, you can also use the AddQueryOption(String, Object) method to add any of the other query options supported by OData to a query.

LINQ queries

Because the DataServiceQuery class implements IQueryable , the OData client library for Windows Phone is able to transform LINQ queries against entity set data into a URI that represents a query expression evaluated against a data service resource. For example, the following LINQ query returns a feed containing a collection of Order entities filtered by the CustomerID property value supplied by the user in the customerId text box.

// Define a query that returns orders for a given customer. 
    var query = from orderByCustomer in context.Orders 
        where orderByCustomer.Customer.CustomerID == this.customerId.Text 
        select orderByCustomer; 

Loading deferred content

By default, OData limits the amount of data that a query returns. However, you can explicitly load additional data, including related entities, paged response data, and binary data streams, from the data service when it is needed. When you execute a query, only entities in the addressed entity set are returned.

For example, when a query against the Northwind data service returns Customers entities, by default the related Orders entities are not returned, even though there is a relationship between Customers and Orders. Related entities can be loaded with the original query (eager loading) or on a per-entity basis (explicit loading).

To explicitly load related entities, you must call either the LoadAsync()()() method on the DataServiceCollection returned by a navigation property or the BeginLoadProperty(Object, String, AsyncCallback, Object) and EndLoadProperty(IAsyncResult) methods on the DataServiceContext class. Do this once for each entity for which you want to load related entities. Each call to these methods results in a new request to the data service. To perform eager load related entries, you must use the Expand(String) method on the DataServiceQuery, which appends the $expand query option to the generated query URI. This loads all related data in a single request, but returns a much larger payload.

Important Note:

When deciding on a pattern for loading related entities, consider the performance tradeoff between message size and the number of requests to the data service.

The following LINQ query shows an example of eager loading of the Order and Order_Details objects that belong to the selected customer.

// Define a query that returns orders along with order details for a given customer.
var query = from orderByCustomer in context.Orders.Expand("Order_Details")
            where orderByCustomer.Customer.CustomerID == this.customerId.Text
            select orderByCustomer;

When paging is enabled in the data service, you must explicitly load subsequent data pages from the data service when the number of returned entries exceeds the paging limit. Because it is not possible to determine in advance when paging can occur, we recommend that you enable your app to properly handle a paged OData feed. To load a paged response, you must call the BeginLoadProperty(Object, String, AsyncCallback, Object) method with the current DataServiceQueryContinuation token. When using a DataServiceCollection class, you can instead call the LoadNextPartialSetAsync()()() method in the same way that you call LoadAsync()()() method. For an example of this loading pattern, see How to consume an OData service for Windows Phone 8.

Working with binary data

OData defines a mechanism for accessing binary data separate from an entity to which it belongs. In this way, an OData service can expose large binary data as a media resource that belongs to a related media link entry. The OData client library for Windows Phone can consume a media resource from an OData service as a binary stream. To access the binary stream, call the BeginGetReadStream(Object, DataServiceRequestArgs, AsyncCallback, Object) method on the DataServiceContext instance that is tracking the entity that is the media link entry. This asynchronous method returns a DataServiceStreamResponse object when the EndGetReadStream(IAsyncResult) method is called on the DataServiceContext. Do this when you want to return the media resource as a stream, which you might use when saving a media resource to isolated storage.

When you bind a media resource to an Image control in the UI, you can instead call the GetReadStreamUri(Object) method to get the edit-media URI of the media resource that is used to create the image during binding. To support data binding, the GetReadStreamUri(Object) method can be called either in the getter of an extension property used for binding or in a value converter used in the binding. For more info, see the post Binding Media Resource Streams to XAML Controls.

To create a new media resource in an OData service, first create a new entity instance and then call the SetSaveStream(Object, Stream, Boolean, String, String) method, providing a Stream object that contains the media resource data for the new media link entry instance. The client sends a POST request to insert the new media resource after the BeginSaveChanges(AsyncCallback, Object) and EndSaveChanges(IAsyncResult) methods are called. For more info, see Accessing a Media Resource Stream from a Windows Phone 7 Application.

Query projection

Projection provides a mechanism to reduce the amount of data in the OData feed returned by a query by specifying that only certain properties of an entity are returned in the response. For more info, see OData: Select System Query Option ($select). You can add a projection clause to a LINQ query by using the select clause (Select in Visual Basic). Returned entity data can be projected into either entity types or non-entity types on the client. Changes made to non-entity types cannot be saved to the data service. For example, the following LINQ query projects Customer data into a new CustomerAddress entity type on the client.

var query = from c in context.Customers
            where c.Country == "Germany"
            select new CustomerAddress
            {
                CustomerID = c.CustomerID,
                Address = c.Address,
                City = c.City,
                PostalCode = c.PostalCode,
                Country = c.Country
            };

Warning

Data loss might occur in the data service when you save updates that were made to projected types. For more info, see Projection Considerations in the WCF Data Services client documentation.

For a complete example, including the definition of the CustomerAddress type on the client, see How to: Project Data Service Query.

Modifying resources and saving changes

Use the AddObject(String, Object), UpdateObject(Object), and DeleteObject(Object) methods on DataServiceContext class to manually track changes on the OData client. These methods enable the client to track added and deleted entities and also changes that you make to property values or to relationships between entity instances.

When the proxy classes are generated, an AddTo method is created for each entity in the DataServiceContext class. Use these methods to add a new entity instance to an entity set and report the addition to the context. Those tracked changes are sent back to the data service asynchronously when you call the BeginSaveChanges(AsyncCallback, Object) and EndSaveChanges(IAsyncResult) methods of the DataServiceContext class.

Note

When you use the DataServiceCollection object to bind data to controls, changes are automatically reported to the DataServiceContext instance and you do not need to report changes to objects in the collection.

The following example shows how to call the BeginSaveChanges(SaveChangesOptions, AsyncCallback, Object) and EndSaveChanges(IAsyncResult) methods to asynchronously send updates to the Northwind data service:

private void saveChanges_Click(object sender, RoutedEventArgs e)
{
    // Start the saving changes operation.
    svcContext.BeginSaveChanges(SaveChangesOptions.Batch, 
        OnChangesSaved, svcContext);
}

private void OnChangesSaved(IAsyncResult result)
{
    // Use the Dispatcher to ensure that the 
    // asynchronous call returns in the correct thread.
    Dispatcher.BeginInvoke(() =>
        {
            svcContext = result.AsyncState as NorthwindEntities;

            try
            {
                // Complete the save changes operation and display the response.
                WriteOperationResponse(svcContext.EndSaveChanges(result));
            }
            catch (DataServiceRequestException ex)
            {
                // Display the error from the response.
                WriteOperationResponse(ex.Response);
            }
            catch (InvalidOperationException ex)
            {
                messageTextBlock.Text = ex.Message;
            }
            finally
            {
                // Set the order in the grid.
                ordersGrid.SelectedItem = currentOrder;
            }
        }
    );
}

Note

The Northwind sample data service that is published on the OData Web site is read-only; attempting to save changes returns an error. To successfully execute this code example, you must create your own Northwind sample data service. To do this, complete the steps in the topic How to: Create the Northwind Data.

Maintaining state during app execution

Apps typically are put into a dormant state when the user navigates away. In this state, the app is preserved in memory so that if the user returns to the app, it can resume almost instantly. This fast app switching is enabled automatically. However, it is still possible that an app will be terminated while it is dormant. It is important that you design your app so that it handles these changes in state that occur throughout the app life cycle. For more info, see App activation and deactivation for Windows Phone 8. To make sure that your app state is not lost when termination occurs, you can implement handlers for events that occur during the app life cycle. In these handlers, you can save and restore the state of the DataServiceContext class as well as any DataServiceCollection instances when your app transitions between active and inactive states. When these two objects are serialized, all tracked or related entity objects are also serialized, including any media resource streams that have not yet been sent to the data service. Relationships between nested collections are also restored when the collections themselves are restored. This behavior creates an experience in which it seems to the user that the app continued to run even if execution is terminated.

The OData client for Windows Phone includes a DataServiceState class that is used to help manage these state transitions. These state management events are handled in the code-behind page of the main app. The following table shows Windows Phone state changes and how to use the DataServiceState class for each change in state.

When deactivating or navigating away from a page, call the Serialize(DataServiceContext, Dictionary<(Of <(String, Object>)>)) method on the DataServiceState class, passing the DataServiceContext instance. Optionally, you can pass a Dictionary<(Of <(TKey, TValue>)>) object that contains named DataServiceCollection instances, including graphs of related collections. The following example shows serializing context and collection data that is stored in the page state.

// Define a dictionary to hold an existing DataServiceCollection<Customer>. 
var collections = new Dictionary<string, object>();
collections.Add("Customers", customers);

// Serialize the data service data into the state dictionary.      
this.State["DataServiceState"] = 
   DataServiceState.Serialize(context, collections);

The serialized and stored DataServiceState is retrieved from the state dictionary. When activating or navigating to a page, call the Deserialize(String) method on the DataServiceState instance, which returns the stored DataServiceContext instance and any stored Dictionary<(Of <(TKey, TValue>)>) object that contains named DataServiceCollection instances. The following example shows deserializing stored data service state data.

object storedState;
DataServiceState state;

// Get the serialized data service state.
if (this.State.TryGetValue("DataServiceState", out storedState))
{
    // Deserialize the DataServiceState object.
    state = DataServiceState.Deserialize(storedState as string);

    // Set the context from the stored object.
    context = (NorthwindEntities)state.Context;

    // Set the binding collections from the stored collection.
    customers = state.RootCollections["Customers"] as DataServiceCollection<Customer>;
}

Tip

Attempting to store tracked entity objects directly in a state dictionary can result in errors during serialization. If you need to maintain the state of individual tracked objects, instead store the URI of the entity. You can get the URI of an entity object by calling the TryGetUri(Object, Uri%) method. Later you can retrieve the object from the restored DataServiceContext by passing the stored URI to the TryGetEntity``1(Uri, UMP%) method.

For more information, see How to persist the state of an OData client for Windows Phone 8.

When you implement an MVVM design pattern for your app, we recommend that you serialize and deserialize data service state in the view model itself. For more information, see Walkthrough: Consuming OData with MVVM for Windows Phone 8.

Client authentication

The OData client library for Windows Phone enables you to specify the credentials supplied to the data service when making the request. You do this by assigning an object that implements the ICredentials interface, such as NetworkCredential, to the Credentials()()() property of the DataServiceContext.

Security Note:

User credentials should be requested only during execution and should not be cached. Credentials must always be stored securely.

The following line of code sets the credentials for requests made to the data service using this DataServiceContext instance.

context.Credentials = 
    new NetworkCredential(userName, password, domain);

The credentials property supports password-based authentication schemes such as basic, digest, and Windows authentication.

Security Note:

Data sent with basic and digest authentication is not encrypted, so the data can be seen by an adversary. Additionally, basic authentication credentials (user name and password) are sent in the clear and can be intercepted.

You can also authenticate a Windows Phone app by using other claims-based authentication mechanisms, such as OAuth 2.0. When using such a claims-based authentication scheme, you must manually set the headers required by the specific claims provider. You must handle the SendingRequest()()() event raised by the DataServiceContext to access and modify the request headers. The post Connecting to an OAuth 2.0 Protected OData Service shows how to do this for a Windows Phone app that authenticates by using Access Control Services (ACS) in Windows Azure AppFabric. For a more general discussion of authentication in OData services, see Securing WCF Data Services.

Message compression

The OData client library for Windows Phone communicates with an OData service by using Atom-formatted HTTP messages. Because Atom is an XML-based format, messages require more bandwidth than the same message formatted by using the JavaScript Object Notation (JSON) format, the other OData-supported format. While the OData client library for Windows Phone does not support the JSON format, you can reduce network bandwidth requirements by using an HTTP compression scheme, such as gzip compression. You set the Accept-Encoding HTTP message header to request that the data service compress the response or to indicate that a POST request is compressed. The value of this header is set to the name of the requested compression scheme.

Important Note:

Message compression reduces network usage for communication between the data service and the Windows Phone device. However, it also consumes additional processing resources both at the data service and on the device. This additional processing can affect the overall performance of the communication and impact the battery life of the device.

The client provides the following events that, when handled, enable you to compress requests to and decompress responses from an OData service that supports compression.

  • ReadingResponse()()()
    This event occurs after a response has been received from the data service but before the message is read. Handle this event to determine if a response is compressed and to decompress the response if needed.

  • WritingRequest()()()
    This event occurs before an HTTP request is generated. Handle this event to set the Accept-Encoding header to request a compressed response or to compress the message body.

When you handle these events, you can access both the message headers and the message body from the supplied ReadingWritingHttpMessageEventArgs. The message headers are accessed from a dictionary of string values returned by the Headers()()() property. The message body is accessed as a stream from the Content()()() property. For more information, see this post on OData compression.

Important Note:

The Windows Phone SDK does not include support for compression. To be able to implement compression, you must use a third-party compression library that supports Windows Phone. You must also enable the requested compression scheme on the web server that hosts the OData service.

See Also

Other Resources

Communications for Windows Phone 8

Web service security for Windows Phone 8

How to consume an OData service for Windows Phone 8

Open Data Protocol (OData) - Developers

WCF Data Services Client Library

WCF Data Service Client Utility (DataSvcUtil.exe)