Powered by

US - English
NEW! Silverlight 5 is available Learn More

Consuming OData Feeds

By Microsoft Silverlight Team|June 21, 2010|Level 300 : Intermediate

Summary

Silverlight includes a WCF Data Services client library that enables you to access data from any service that exposes an Open Data Protocol (OData) feed. OData is based on an entity and relationship model that enables you to access data in the style of representational state transfer (REST) resources. Silverlight-based applications can access this data through the standard HTTP protocol to execute queries, and to create, update, and delete data in the data service. OData enables you to create services that expose data to a variety of client applications. For more information, see the OData Web site.

This QuickStart describes how to create a Silverlight client that can consume an OData feed and display feed data in bound data controls. This QuickStart contains the following sections:

To view the complete source code for this QuickStart application, see the Silverlight SDK Sample Browser.

Querying an OData Service

This QuickStart shows you how to query an OData-based data service and bind returned data to controls in a Silverlight application. When you click Start in the Silverlight application below, a URI-based query is sent to the Northwind sample data service and an OData feed is returned that contains all Customer entities. The exact URI of each query is displayed in the Query Resources box.

When you click the Start button, the LoadAsync method is called on the DataServiceCollection<T> to execute the query and load entities from the response into the binding collection as Customer objects. The following method in the code-behind page for MainPage.xml asynchronously executes a query against the data service for all Customer entities in the Customers entity set:

C#

private void startButton_Click(object sender, RoutedEventArgs e)
{
    // Instantiate the context based on the data service URI.
    context = new NorthwindEntities(serviceUri);

    // Instantiate the binding collection.
    customerBindingCollection = new DataServiceCollection<Customer>();

    // Register a handler for the LoadCompleted event.
    customerBindingCollection.LoadCompleted += 
        new EventHandler<LoadCompletedEventArgs>(customerBindingCollection_LoadCompleted);

    // Define a query that returns all customers.
    DataServiceQuery<Customer> query = context.Customers;

    // Execute the query.
    customerBindingCollection.LoadAsync(query);
}

Visual Basic

Private Sub startButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
    ' Instantiate the context based on the data service URI.
    context = New NorthwindEntities(serviceUri)

    ' Instantiate the binding collection.
    customerBindingCollection = New DataServiceCollection(Of Customer)()

    ' Register a handler for the LoadCompleted event.
    AddHandler customerBindingCollection.LoadCompleted, _
        AddressOf customerBindingCollection_LoadCompleted

    ' Define a LINQ query that returns all customers.
    Dim query = context.Customers

    ' Execute the query.
    customerBindingCollection.LoadAsync(query)
End Sub

When the query completes, the WCF Data Services client parses the returned OData feed and raises the LoadCompleted event.

Handling a Paged Response

To improve performance, a data service can return an OData feed as a series of paged responses instead of in a single response. When paging is enabled on the data service, each page has a URI that is used to return the next page in the feed. The final page in a feed doesn't have a next page link. The client encapsulates this next link URI as a DataServiceQueryContinuation<T> object. The following method handles the LoadCompleted event for the query for Customers that was started asynchronously in the previous example:

C#

private void customerBindingCollection_LoadCompleted(object sender, 
                                                     LoadCompletedEventArgs e)
{
    // Get the binding collection that executed the query.
    var binding = (DataServiceCollection<Customer>)sender;

    if (e.Error == null)
    {
         // Consume a data feed that contains paged results.
        if (binding.Continuation != null)
        {
            // If there is a continuation token, 
            // load the next page of results.
            binding.LoadNextPartialSetAsync();                    
        }
        else
        {
            // Since there are no remaining results pages, 
            // bind the collection to the view source.            
            CollectionViewSource customersViewSource = 
                (CollectionViewSource)this.Resources["customersViewSource"];
            customersViewSource.Source = binding;
        }
    }
    else
    {
        // Display the error returned by the data service.
        ChildWindow cw = new ChildWindow();
        cw.Content = string.Format("An error has occured: {0}", e.Error.Message);
        cw.Show();
    }
 }

Visual Basic

Private Sub customerBindingCollection_LoadCompleted(ByVal sender As Object, _
                                                    ByVal e As LoadCompletedEventArgs)
    ' Get the binding collection that executed the query.
    Dim binding = CType(sender, DataServiceCollection(Of Customer))

    If e.Error Is Nothing Then
        ' Consume a data feed that contains paged results.
        If Not binding.Continuation Is Nothing Then

            ' If there is a continuation token, load the next page of results.
            binding.LoadNextPartialSetAsync()

        Else
            ' Since there are no remaining results pages, 
            ' bind the collection to the view source.
            Dim customersViewSource As CollectionViewSource = _
                CType(Me.Resources("customersViewSource"), CollectionViewSource)
            customersViewSource.Source = binding
        End If
    Else
        ' Display the error returned by the data service.
        Dim cw As ChildWindow = New ChildWindow()
        cw.Content = String.Format("An error has occured: {0}", e.Error.Message)
        cw.Show()
    End If
End Sub

When you handle the LoadCompleted event in your code, the client makes sure that the response is returned to the correct thread.

We check if there is a continuation token in the response object. If the token is null (Nothing in Visual Basic), then there are no remaining pages to load. Otherwise, we call LoadNextPartialSetAsync to load the next page of the feed. (As you can see in this QuickStart sample, the Customers feed from the public sample Northwind data service is returned as a series of five pages.) After all pages of the Customers feed are loaded into the DataServiceCollection<T> binding collection, the source of the root CollectionViewSource object in the XAML page is set to this loaded binding collection. For more on binding, see the section Binding Collections to Controls.

Loading Related Data

This QuickStart displays data from Customer, Order, and Order_Detail entities. However, when the Start button is clicked, only the Customer data feed is returned by the initial query. This reduces the amount of data that is returned by a single request. In the Northwind data model, each Customer entity has a collection of related Order entities that are accessed by using the Orders navigation property. Based on this relationship in the model, the Add Service Reference dialog creates an Orders property on the Customer type that returns a DataServiceCollection<T> of Order objects that belong to the Customer. When a specific customer is selected in the ComboBox, we load a new feed that contains the related Order entities. The following code handles the SelectionChanged event raised by the customerComboBox:

C#

private void customersComboBox_SelectionChanged(object sender, 
                                                SelectionChangedEventArgs e)
{
    // Get the selected Customer.
    Customer selectedCustomer = ((ComboBox)sender).SelectedItem as Customer;

    if (selectedCustomer != null)
    {
        // Register a handler for the LoadCompleted event on the 
        // child Orders binding collection.
        selectedCustomer.Orders.LoadCompleted += 
            new EventHandler<LoadCompletedEventArgs>(Orders_LoadCompleted);

        if (selectedCustomer.Orders.Count == 0)
        {
            // Load the related Orders for the selected Customer,
            // if they are not already loaded.
            selectedCustomer.Orders.LoadAsync();
        }
    }
}

Visual Basic

Private Sub customersComboBox_SelectionChanged(ByVal sender As Object, _
                                               ByVal e As SelectionChangedEventArgs)
    ' Get the selected Customer.
    Dim selectedCustomer As Customer = _
        CType(CType(sender, ComboBox).SelectedItem, Customer)

    If Not selectedCustomer Is Nothing Then

        ' Register a handler for the LoadCompleted event 
        ' on the child Orders binding collection.
        AddHandler selectedCustomer.Orders.LoadCompleted,
            AddressOf Orders_LoadCompleted

        If selectedCustomer.Orders.Count = 0 Then
            ' Load the related Orders for the selected Customer,
            ' if they are not already loaded.
            selectedCustomer.Orders.LoadAsync()
        End If
    End If
End Sub

In this method, the LoadAsync method is called on the DataServiceCollection<T> that is returned by the Orders property of the selected Customer object. When LoadAsync is called, the client generates a query that returns an OData feed that contains all Order entities that are related to the selected Customer. When the query returns, the LoadCompleted event is raised by the new binding collection of Order objects. This event is handled by the following method:

C#

private void Orders_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
    // Get the binding collection that executed the query.
    var binding = sender as DataServiceCollection<Order>;

    if (e.Error == null)
    {
        // Consume a data feed that contains paged results.
        if (binding.Continuation != null)
        {
            // If there is a continuation token, 
            // load the next page of results.
            binding.LoadNextPartialSetAsync();
        }
        else
        {
            // When all orders are loaded, select the first Order in the ComboBox.
            if (orderIDComboBox.Items.Count > 0)
            {
                orderIDComboBox.SelectedIndex = 0;
            }
        }
    }
    else
    {
        // Display the error returned by the data service.
        ChildWindow cw = new ChildWindow();
        cw.Content = string.Format("An error has occured: {0}", e.Error.Message);
        cw.Show();
    }
}

Visual Basic

Private Sub Orders_LoadCompleted(ByVal sender As Object, _
                                 ByVal e As LoadCompletedEventArgs)
    ' Get the binding collection that executed the query.
    Dim binding = CType(sender, DataServiceCollection(Of Order))

    If e.Error Is Nothing Then
        ' Consume a data feed that contains paged results.
        If Not binding.Continuation Is Nothing Then

            ' If there is a continuation token, load the next page of results.
            binding.LoadNextPartialSetAsync()
        Else
            ' When all orders are loaded, select the first Order in the ComboBox.
            If (orderIDComboBox.Items.Count > 0) Then
                orderIDComboBox.SelectedIndex = 0
            End If
        End If
    Else
        ' Display the error returned by the data service.
        Dim cw As ChildWindow = New ChildWindow()
        cw.Content = String.Format("An error has occured: {0}", e.Error.Message)
        cw.Show()
    End If
End Sub

As before when querying for the Customers feed, the query that loads the related Orders might be returned as a feed with more than one page. Because of this, we need to check for a continuation token in the response. If the token is present, the next page in the feed is loaded. When all pages are loaded, the first Order in the orderIDComboBox is selected. This calls the handler for the SelectedChanged event, which uses the same process to load the Order_Detail objects that are related to the first Order object in the binding collection. Because all of the CollectionViewSource elements are dependent on the original instance, named customerViewSource, we only need to set the Source property of this instance to bind all of the controls on the page to their respective binding collections, as was shown in a previous code example.

To view the complete source code for this QuickStart application, see the Silverlight SDK Sample Browser.

Binding Collections to Controls

When you use the Add Service Reference dialog to add a reference to an OData-based service, Visual Studio creates a project data source for the entity sets exposed by the data service. In this QuickStart, the NorthwindEntities class is the container for the entity sets exposed by the public Northwind sample data service, such as Customers, Orders, and Order_Details. When you drag the Customers entity set from the Data Sources window onto the design surface of the page, a CollectionViewSource element named customersViewSource is created in the XAML, along with the control that displays Customer object data. Because Orders has a master-detail relationship to a Customer, doing the same for the Orders entity set creates a new CollectionViewSource, the data source of which is the customerViewSource element. The same behavior applies to the master-detail relationship between Orders and Order_Details. The following XAML fragment defines the set of CollectionViewSource elements for the QuickStart:

XAML

<UserControl.Resources>
    <CollectionViewSource x:Key="customersViewSource" />
    <CollectionViewSource x:Key="customersOrdersViewSource" 
                          Source="{Binding Path=Orders, 
                                    Source={StaticResource customersViewSource}}" />
    <CollectionViewSource x:Key="customersOrdersOrder_DetailsViewSource" 
                          Source="{Binding Path=Order_Details, 
                                 Source ={StaticResource customersOrdersViewSource}}" />
</UserControl.Resources>

To view the complete XAML that defines the MainPage.xaml file for this QuickStart application, see the Silverlight SDK Sample Browser.

Saving Changes to Data

When a data service is configured to accept requests to change data, the WCF Data Services client for Silverlight makes it easy to propagate changes made in data-bound controls back to the data service. The binding collections listen for changes made to data in the data-bound controls and these changes are reported to the DataServiceContext, which in this QuickStart is an instance of NorthwindEntities. These changes are sent to the data service by calling BeginSaveChanges followed by EndSaveChanges, using the Dispatcher to marshal the response back to the correct thread. However, because the public Northwind sample data service is read-only, the QuickStart does not demonstrate this functionality. For an example of how to do this, see the MSDN topic Modifying and Deleting Entity Data (WCF Data Services/Silverlight).

Add Service Reference

You can use the Add Service Reference dialog in Visual Studio to add a reference to an OData-based service. This enables you to more easily consume OData feeds in a client application that you develop in Visual Studio. When you complete this procedure, data classes are generated based on metadata that is obtained from the data service. The following image shows the Add Service Reference dialog being used to add a reference to the public Northwind Data Service to this QuickStart:

Add Service Reference dialog

For more information, see Generating the Data Service Client Library (WCF Data Services).

Cross-Domain Requests

It is important to note that a request to the sample Northwind data service is a cross-domain call. It works in this QuickStart because the data services hosted on the www.odata.org site have a cross-domain file that enables it. The cross-domain file can be accessed from http://services.odata.org/clientaccesspolicy.xml, which is in the root of the public data services hosted at OData.org. Other data services must be similarly enabled for cross-domain calls to succeed. For more information, see Making a Service Available Across Domain Boundaries in the Silverlight documentation on MSDN.

See Also

Microsoft Silverlight Team

By Microsoft Silverlight Team, Silverlight is a powerful development platform for creating engaging, interactive user experiences for Web, desktop, and mobile applications when online or offline.

Comments (0)