How to: Bind Data Service Data to Controls (WCF Data Services/Silverlight)

Microsoft Silverlight will reach end of support after October 2021. Learn more.

With WCF Data Services, you can bind Silverlight controls, such as a ListBox or ComboBox, to an instance of DataServiceCollection<T>. This class handles the events raised by the controls to keep the DataServiceContext synchronized with changes that are made to data in the controls. A DataServiceCollection<T> is defined based on a DataServiceQuery<TElement>. This query, when it is executed, returns an Open Data Protocol (OData) feed that provides the objects for the collection.

The procedures in this topic show how to perform the following tasks:

  • (Optional) enable paging functionality in the Northwind data service.

  • Create a new Silverlight application.

  • Generate client data service classes that support automatic data binding.

  • Query the data service.

  • Bind the results to controls in the application.

Use a CollectionViewSource instance to simplify the binding of master/detail Order and Order_Detail objects. To do this, set the Source property of the CollectionViewSource to the master DataServiceCollection<T>. The data binding of controls is defined in the XAML such that changing the selected Order changes the displayed Order_Details when the collection is loaded. For more information, see How to: Bind to Hierarchical Data and Create a Master/Details View.

NoteNote:

The CollectionViewSource class is not supported in Silverlight 3.

This example shows how to bind an OData feed when paging is enabled in the data service. Paging of a data feed is supported by version 2.0 of the OData Protocol.

The Northwind sample data service accessed by the application is created when you complete the procedures in the topic How to: Create the Northwind Data Service (WCF Data Services/Silverlight). You can also use the Northwind sample data service that is published on the OData Web site; this sample data service is read-only and attempting to save changes returns an error.

To enable paging in the Northwind sample data service

  1. In Solution Explorer under your ASP.NET project, double-click Northwind.svc.

    This opens the code page for the Northwind sample data service.

  2. In the code for the data service, append the following code to the InitializeService method:

    ' Set the data service version to V2 to support paging.
    config.DataServiceBehavior.MaxProtocolVersion = _
        System.Data.Services.Common.DataServiceProtocolVersion.V2
    
    ' Set paging limits for the Customers and Orders entity sets.
    ' These limits are set very low to demonstrate paging with the 
    ' Northwind database. Paging should be configured to optimize
    ' data service performance.
    config.SetEntitySetPageSize("Orders", 2)
    config.SetEntitySetPageSize("Order_Details", 2)
    
    // Set the data service version to V2 to support paging.
    config.DataServiceBehavior.MaxProtocolVersion = 
        System.Data.Services.Common.DataServiceProtocolVersion.V2;
    
    // Set paging limits for the Customers and Orders entity sets.
    // These limits are set very low to demonstrate paging with the 
    // Northwind database. Paging should be configured to optimize
    // data service performance.
    config.SetEntitySetPageSize("Orders", 2);
    config.SetEntitySetPageSize("Order_Details", 2);
    

    This enables paging for the Orders and Order_Details entity sets.

To create the application for Silverlight project

  1. In Solution Explorer, right-click the Solution, point to Add, and then select New Project.

  2. In the Add New Project dialog box, select Silverlight from the Categories pane, and then select the Silverlight Application template. Name the project DataBindingSample.

  3. In the Add Silverlight Application dialog box, select Host the Silverlight application in a new or existing Web site in the solution. Select Add a test page that references the application and Make it the start page.

  4. Click OK.

    This creates the application for Silverlight.

To add a data service reference to the project

  1. Right-click the DataBindingSample project, click Add Service Reference, and then click Discover.

    This displays the Northwind data service that you created in the first task.

  2. In the Namespace text box, type Northwind, and then click OK.

    This adds a new code file to the project, which contains the data classes that are used to access and interact with data service resources as objects. The data classes are created in the namespace DataBindingSample.Northwind.

To define the client application user-interface

  1. In Solution Explorer under DataBindingSample, right-click References and click Add Reference.

    This displays the Add Reference dialog box.

  2. Select System.Windows.Controls.Data and click OK.

  3. In Solution Explorer, double-click the MainPage.xaml file. This opens XAML markup for the Page class that is the user interface for the Silverlight application.

  4. Replace the existing XAML markup with the following markup that defines the application user-interface:

    <!-- NOTE: 
      By convention, the sdk prefix indicates a URI-based XAML namespace declaration 
      for Silverlight SDK client libraries. This namespace declaration is valid for 
      Silverlight 4 only. In Silverlight 3, you must use individual XAML namespace 
      declarations for each CLR assembly and namespace combination outside the scope 
      of the default Silverlight XAML namespace. For more information, see the help 
      topic "Prefixes and Mappings for Silverlight Libraries". 
    -->
    <UserControl  x:Class="MainPage"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"            
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Loaded="MainPage_Loaded" mc:Ignorable="d" 
                  xmlns:d="https://schemas.microsoft.com/expression/blend/2008" 
                  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" 
                  xmlns:my="clr-namespace:DataBindingSample.Northwind" 
                  d:DesignHeight="371" d:DesignWidth="0">
        <UserControl.Resources>
            <CollectionViewSource x:Key="OrdersViewSource" 
                                  d:DesignSource="{d:DesignInstance my:Order, CreateList=True}" />
            <CollectionViewSource x:Key="OrdersOrder_DetailsViewSource" 
                                  Source="{Binding Path=Order_Details, Source={StaticResource OrdersViewSource}}" />
        </UserControl.Resources>
        <StackPanel Orientation="Vertical" Margin="10" Height="Auto" Name="LayoutRoot" Width="450">
            <StackPanel Orientation="Horizontal">
                <sdk:Label Content="Customer ID:"/>
                <TextBox Name="customerId" Text="ALFKI" Margin="10" Width="100"/>
                <Button Name="getCustomerOrders" Content="Get Orders" Height="25" Width="80" 
                        Click="getCustomerOrders_Click" />
            </StackPanel>
            <StackPanel Name="ordersStackPanel" VerticalAlignment="Top" Orientation="Vertical"  
                        DataContext="{StaticResource OrdersViewSource}">
                <Grid HorizontalAlignment="Left" Name="ordersGrid" VerticalAlignment="Top">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <sdk:Label Content="Order:" Grid.Column="0" Grid.Row="0" 
                               HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                    <ComboBox DisplayMemberPath="OrderID" Grid.Column="1" Grid.Row="0" Height="23" 
                              HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="3" 
                              Name="OrderIDComboBox" VerticalAlignment="Center" Width="120" 
                              SelectionChanged="ordersList_SelectionChanged">
                        <ComboBox.ItemsPanel>
                            <ItemsPanelTemplate>
                                <VirtualizingStackPanel />
                            </ItemsPanelTemplate>
                        </ComboBox.ItemsPanel>
                    </ComboBox>
                    <sdk:Label Content="Freight:" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" 
                               Margin="3" VerticalAlignment="Center" />
                    <TextBox Grid.Column="3" Grid.Row="0" Height="23" HorizontalAlignment="Left" 
                             Margin="3" Name="FreightTextBox" Text="{Binding Path=Freight, Mode=TwoWay, 
                        ValidatesOnExceptions=true, NotifyOnValidationError=true}" 
                             VerticalAlignment="Center" Width="120" />
                    <sdk:Label Content="Required Date:" Grid.Column="0" Grid.Row="1" 
                               HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
                    <sdk:DatePicker Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" 
                                    Margin="3" Name="RequiredDateDatePicker" 
                                    SelectedDate="{Binding Path=RequiredDate, Mode=TwoWay, 
                        ValidatesOnExceptions=true, NotifyOnValidationError=true}" 
                                    VerticalAlignment="Center" Width="120" />
                    <sdk:Label Content="Order Date:" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Left" 
                               Margin="3" VerticalAlignment="Center" />
                    <TextBox Grid.Column="3" Grid.Row="1" Height="23" HorizontalAlignment="Left" 
                             Width="120" Margin="3" Name="OrderDateTextBlock" Text="{Binding Path=OrderDate}" 
                             VerticalAlignment="Center" />
                </Grid>
            </StackPanel>
            <StackPanel DataContext="{StaticResource OrdersOrder_DetailsViewSource}" Orientation="Vertical">
                <sdk:Label Content="Order items:" Margin="10"/>
                <sdk:DataGrid AutoGenerateColumns="False" Height="170" ItemsSource="{Binding}" 
                              Name="Order_DetailsDataGrid"  RowDetailsVisibilityMode="VisibleWhenSelected" 
                              Width="400">
                    <sdk:DataGrid.Columns>
                        <sdk:DataGridTextColumn x:Name="ProductIDColumn" Binding="{Binding Path=ProductID}" 
                                                Header="Product" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="QuantityColumn" Binding="{Binding Path=Quantity}" 
                                                Header="Quantity" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="DiscountColumn" Binding="{Binding Path=Discount}" 
                                                Header="Discount" Width="SizeToHeader" />
                        <sdk:DataGridTextColumn x:Name="UnitPriceColumn" Binding="{Binding Path=UnitPrice}" 
                                                Header="Unit Price" Width="SizeToHeader" />
                    </sdk:DataGrid.Columns>
                </sdk:DataGrid>
            </StackPanel>
            <Button Name="saveChangesButton" Content="Save Changes" 
                    HorizontalAlignment="Right" Click="saveChangesButton_Click"  
                    Width="100" Height="25" Margin="10"/>
    
        </StackPanel>
    </UserControl>
    
    NoteNote:

    For a C# application, you must include the namespace in the Class attribute of the UserControl. The default namespace is not required for a Visual Basic application.

To add the code that binds data service data to controls in the Silverlight application

  1. In Solution Explorer under DataBindingSample, open the code page for the MainPage.xaml file, and add the following using statement (Imports in Visual Basic).

    using System.Windows.Data;
    using System.Data.Services.Client;
    using DataBindingSample.Northwind;
    
  2. Add the following declarations to the MainPage class:

    Dim context As NorthwindEntities
    Dim trackedOrders As DataServiceCollection(Of Order)
    Dim selectedOrder As Order
    Dim ordersViewSource As CollectionViewSource
    
    NorthwindEntities context;
    DataServiceCollection<Order> trackedOrders;
    Order selectedOrder;
    CollectionViewSource ordersViewSource; 
    
  3. Add the following MainPage_Loaded method to the MainPage class:

    Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Initialize the data service context.
        context = _
                New NorthwindEntities(New Uri("https://localhost:54321/Northwind.svc"))
    
        ' Initialize the binding and view source collections.
        trackedOrders = New DataServiceCollection(Of Order)()
        ordersViewSource = CType(Me.Resources("OrdersViewSource"), CollectionViewSource)
    
        ' Define a handler for the LoadCompleted event of the collection.
        AddHandler trackedOrders.LoadCompleted, _
        AddressOf trackedOrders_LoadCompleted
    End Sub
    
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        // Initialize the data service context.
        context =
            new NorthwindEntities(new Uri("Northwind.svc", UriKind.Relative));
    
        // Initialize the binding and view source collections.
        trackedOrders = new DataServiceCollection<Order>();
        ordersViewSource = (CollectionViewSource)this.Resources["OrdersViewSource"];
    
        // Define a handler for the LoadCompleted event of the binding collection.
        trackedOrders.LoadCompleted +=
            new EventHandler<LoadCompletedEventArgs>(trackedOrders_LoadCompleted);
    }
    

    When the page is loaded, this code initializes the binding collections and content, and registers the method to handle the LoadCompleted event raised by the binding collection.

  4. Insert the following code into the MainPage class:

    Private Sub getCustomerOrders_Click(ByVal sender As Object, _
                                        ByVal e As RoutedEventArgs)
    
        ' Define a query that returns orders for a give customer.
        Dim query = From orderByCustomer In context.Orders _
                        Where orderByCustomer.Customer.CustomerID = _
                        Me.customerId.Text _
                        Select orderByCustomer
    
        ' Asynchronously load the result of the query.
        trackedOrders.LoadAsync(query)
    
        ' Disable the button until the loading is complete.
        getCustomerOrders.IsEnabled = False
    End Sub
    Private Sub trackedOrders_LoadCompleted(ByVal sender As Object, _
                                            ByVal e As LoadCompletedEventArgs)
        If e.Error Is Nothing Then
            ' Load all pages of Orders before binding.
            If trackedOrders.Continuation IsNot Nothing Then
                trackedOrders.LoadNextPartialSetAsync()
            Else
    
                ' Bind the root StackPanel element to the collection
                ' related object binding paths are defined in the XAML.
                ordersViewSource.Source = trackedOrders
    
                ' Re-enable the button since the loading is complete.
                getCustomerOrders.IsEnabled = True
            End If
        Else
            MessageBox.Show(String.Format("An error has occured: {0}", e.Error.Message))
            getCustomerOrders.IsEnabled = True
        End If
    End Sub
    
    private void getCustomerOrders_Click(object sender, RoutedEventArgs e)
    {
        // Reset the grids.
        ordersViewSource.Source = null;
    
        // Define a query that returns orders for a give customer.
        var query = from orderByCustomer in context.Orders
                    where orderByCustomer.Customer.CustomerID == this.customerId.Text
                    select orderByCustomer;
    
        // Asynchronously load the result of the query.
        trackedOrders.LoadAsync(query);
    
        // Disable the button until the loading is complete.
        getCustomerOrders.IsEnabled = false;
    }
    
    private void trackedOrders_LoadCompleted(object sender, LoadCompletedEventArgs e)
    {
        if (e.Error == null)
        {
            // Load all pages of Orders before binding.
            if (trackedOrders.Continuation != null)
            {
                trackedOrders.LoadNextPartialSetAsync();
            }
            else
            {
                // Bind the root StackPanel element to the collection;
                // related object binding paths are defined in the XAML.
                ordersViewSource.Source = trackedOrders;
    
                // Re-enable the button since the loading is complete.
                getCustomerOrders.IsEnabled = true;
            }
        }
        else
        {
            MessageBox.Show(string.Format("An error has occured: {0}",e.Error.Message));
            getCustomerOrders.IsEnabled = true;
        }
    }
    

    When the getCustomerOrders button is clicked, the following operations are performed:

    • The LoadAsync method is called on the binding collection to execute the supplied query that returns orders, filtered by the supplied customer ID.

    • The LoadNextPartialSetAsync method is called to load subsequent results pages as long as the Continuation property returns a value.

    • The collection of loaded Order objects is bound to the Source property of the CollectionViewSource, which is the master binding object for all controls in the page.

  5. Insert the following code into the MainPage class:

    Private Sub ordersList_SelectionChanged(ByVal sender As Object, _
                                            ByVal e As SelectionChangedEventArgs)
        ' Get the selected Order in the DataGrid.    
        Dim ordersList As ComboBox = CType(sender, ComboBox)
        selectedOrder = CType(ordersList.SelectedItem, Order)
    
        If Not selectedOrder Is Nothing Then
            ' Asynchronously load related items, if they are not already loaded.
            If selectedOrder.Order_Details.Count = 0 Then
    
                ' Register the method to handle the LoadCompleted event.
                AddHandler selectedOrder.Order_Details.LoadCompleted, _
                AddressOf Order_Details_LoadCompleted
    
                Try
                    ' Load the related items.
                    selectedOrder.Order_Details.LoadAsync()
                Catch ex As InvalidOperationException
                    MessageBox.Show(String.Format("An error has occured: {0}", _
                            ex.Message))
                End Try
            End If
        End If
    End Sub
    Private Sub Order_Details_LoadCompleted(ByVal sender As Object, _
                                            ByVal e As LoadCompletedEventArgs)
        Dim trackedItems As DataServiceCollection(Of Order_Detail) = _
            CType(sender, DataServiceCollection(Of Order_Detail))
    
        ' Load any remaining pages of Order_Details.
        If Not trackedItems.Continuation Is Nothing Then
            Try
                trackedItems.LoadNextPartialSetAsync()
            Catch ex As InvalidOperationException
                MessageBox.Show(String.Format("An error has occured: {0}", _
                        ex.Message))
            End Try
        End If
    End Sub
    
    private void ordersList_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // Get the selected Order in the DataGrid.    
        ComboBox ordersList = sender as ComboBox;
        selectedOrder = ((Order)(ordersList.SelectedItem));
    
        if (selectedOrder != null)
        {
            // Asynchronously load related items, if they are not already loaded.
            if (selectedOrder.Order_Details.Count == 0)
            {
                // Register the method to handle the LoadCompleted event.
                selectedOrder.Order_Details.LoadCompleted +=
                    new EventHandler<LoadCompletedEventArgs>(
                        Order_Details_LoadCompleted);
                try
                {
                    // Load the related items.
                    selectedOrder.Order_Details.LoadAsync();
                }
                catch (InvalidOperationException ex)
                {
                    MessageBox.Show(string.Format("An error has occured: {0}",
                        ex.Message));
                }
            }
        }
    }
    
    void Order_Details_LoadCompleted(object sender, LoadCompletedEventArgs e)
    {
        DataServiceCollection<Order_Detail> trackedItems = 
            sender as DataServiceCollection<Order_Detail>;
    
        // Load any remaining pages of Order_Details.
        if (trackedItems.Continuation != null)
        {
            try
            {
                trackedItems.LoadNextPartialSetAsync();
            }
            catch (InvalidOperationException ex)
            {
                MessageBox.Show(string.Format("An error has occured: {0}",
                    ex.Message));
            }
        }
    }
    

    The ordersList_SelectionChanged method handles the SelectionChanged event. When the user selects an order in the ComboBox, the following operations are performed:

  6. Insert the following code that saves changes into the MainPage class:

    ' We need to persist the result of an operation 
    ' to be able to invoke the dispatcher.
    Private currentResult As IAsyncResult
    Private Sub saveChangesButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        ' Define the delegate to callback into the process
        Dim callback As AsyncCallback = AddressOf OnChangesSaved
        Try
            ' Start the saving changes operation. This needs to be a 
            ' batch operation in case we are added a new object with 
            ' a new relationship.
            context.BeginSaveChanges(SaveChangesOptions.Batch, _
                    callback, context)
    
        Catch ex As Exception
            MessageBox.Show(String.Format( _
                            "The changes could not be saved to the data service.\n" _
                            & "The following error occurred: {0}", ex.Message))
        End Try
    End Sub
    Private Sub OnChangesSaved(ByVal result As IAsyncResult)
        ' Persist the result for the delegate.
        currentResult = result
    
        ' Use the Dispatcher to ensure that the 
        ' asynchronous call returns in the correct thread.
        Dispatcher.BeginInvoke(AddressOf ChangesSavedByDispatcher)
    End Sub
    Private Sub ChangesSavedByDispatcher()
    
        Dim errorOccured As Boolean = False
        context = CType(currentResult.AsyncState, NorthwindEntities)
    
        Try
            ' Complete the save changes operation and display the response.
            Dim response As DataServiceResponse = _
            context.EndSaveChanges(currentResult)
    
            For Each changeResponse As ChangeOperationResponse In response
                If changeResponse.Error IsNot Nothing Then errorOccured = True
            Next
            If Not errorOccured Then
                MessageBox.Show("The changes have been saved to the data service.")
            Else
                MessageBox.Show("An error occured. One or more changes could not be saved.")
            End If
        Catch ex As Exception
            ' Display the error from the response.
            MessageBox.Show(String.Format("The following error occured: {0}", ex.Message))
        End Try
    End Sub
    
    private void saveChangesButton_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            // Start the saving changes operation. This needs to be a 
            // batch operation in case we are added a new object with 
            // a new relationship.
            context.BeginSaveChanges(SaveChangesOptions.Batch,
                OnChangesSaved, context);
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("The changes could not be saved to the data service.\n"
                + "The following error occurred: {0}", ex.Message));
        }
    }
    
            private void OnChangesSaved(IAsyncResult result)
            {
                bool errorOccured = false;
    
                // Use the Dispatcher to ensure that the 
                // asynchronous call returns in the correct thread.
                Dispatcher.BeginInvoke(() =>
                {
                    context = result.AsyncState as NorthwindEntities;
    
                    try
                    {
                        // Complete the save changes operation and display the response.
                        DataServiceResponse response = context.EndSaveChanges(result);
    
                        foreach (ChangeOperationResponse changeResponse in response)
                        {
                            if (changeResponse.Error != null) errorOccured = true;
                        }
                        if (!errorOccured)
                        {
                            MessageBox.Show("The changes have been saved to the data service.");
                        }
                        else
                        {
                            MessageBox.Show("An error occured. One or more changes could not be saved.");
                        }
                    }
                    catch (Exception ex)
                    {
                        // Display the error from the response.
                        MessageBox.Show(string.Format("The following error occured: {0}", ex.Message));
                    }
                }
                );
            }
    

    This code asynchronously sends changes that were made in the data bound controls back to the data service.