Sample: Retrieve Multiple Records Using the REST Endpoint with Silverlight

[Applies to: Microsoft Dynamics CRM 2011]

This sample code is for Microsoft Dynamics CRM 2011, and can be found in the following location in the SDK download:

SDK/SampleCode/CS/Silverlight/RESTRetrieveMultipleSilverlight

If you just want to see how this sample works you can install (import) the RESTRetrieveMultipleSilverlight_1_0_0_0_managed.zip managed solution included in the download files. If you install this managed solution and want to create the Web resources using the names below, your solution publisher customization prefix cannot be “sample” unless you uninstall (delete) the managed solution.

Requirements

This sample has the following user interface:

Sample User Interface

This sample uses the following two Web resources:

  • sample_/ClientBin/RESTRetrieveMultipleSilverlight.xap
    The name of this Web resource just reflects the relative output location of the .XAP file in the Microsoft Visual Studio 2010 Silverlight application (version 4) project.
  • sample_/RESTRetrieveMultipleSilverlightTestPage.html
    An HTML page can be used to view the Microsoft Silverlight control outside a form. The only purpose of this Web resource is to provide the URL of the server when the Silverlight control cannot access it itself through the Xrm.Page.context.getServerUrl function when the Silverlight Web resource is added in an entity form.

Note

The customization prefix ” sample_” is not used in code. These samples will work using the customization prefix from any publisher. However, the relative path of the simulated Scripts and Styles folder must be included in the name of the Web resources.

Demonstrates

By default the number of records returned by the REST Endpoint is limited to 50 records. This sample shows how to retrieve multiple records without regard to this limitation.

This sample demonstrates how a user-defined object (PagingContext) can be used to transfer state between the start of the operation and the callback defined by ProcessPages. The first two properties of PagingContext (Query and ServiceContext) are required in order to navigate through the pages. When instantiated, the last property (PageProcessor) is set to a delegate function that will be called for each set of records to process the results. PageProcessor contains the actions to be performed in the application. In this case to simply add more account records to the AccountCollection variable.

The ProcessPages callback method determines if any more records exist and if they do, it uses the DataServiceQueryContinuation(T) Class. The DataServiceQueryContinuation class contains a single property, NextLinkUri which provides URL that contains the $skiptoken parameters that describes the paging boundary. This DataServiceQueryContinuation instance is passed through using the crmContext.BeginExecute method along with the ProcessPages callback method and the context information containing information about the state of the operation.

Although this sample uses the PagingContext and ProcessPages classes to retrieve account records, they can be used for any type of entity.

Preliminary Steps

This Silverlight application uses a service reference named CrmODataServices added using the steps detailed under Generating WCF Data Services Client Data Service Classes. The name of the System.Data.Services.Client.DataServiceContext class was renamed to crmContext from the default value that matched the name of the Microsoft Dynamics CRM Organization.

sample_/ClientBin/RESTRetrieveMultipleSilverlight.xap

This Silverlight file is compiled from files in a Microsoft Visual Studio 2010 Silverlight application (version 4) project named ‘RESTRetrieveMultipleSilverlight’. The only files that were created or modified are as follows:

  • MainPage.xaml
    The default main page of the Silverlight Application.
  • MainPage.xaml.cs
    The Visual C# code-behind file to support MainPage.xaml.
  • ServerUtility.cs
    A static class that provides functions to retrieve the server URL.

MainPage.xaml

    <UserControl x:Class="Microsoft.Crm.Sdk.Samples.MainPage"
             xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="500"
             xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">

    <Grid x:Name="LayoutRoot"
          Background="White"
          Height="300"
          Width="500">
        <Grid.RowDefinitions>
            <RowDefinition Height="250*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>

        <Grid x:Name="ButtonGrid"
              Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="350" />
                <ColumnDefinition Width="150" />
            </Grid.ColumnDefinitions>
            <Button x:Name="btnRetrieveAccounts"
                    Content="Retrieve Accounts"
                    Grid.Column="1"
                    Height="23"
                    Margin="5"
                    VerticalAlignment="Center"
                    Width="110"
                    Click="btnRetrieveAccounts_Click" />
            <Grid Grid.ColumnSpan="1"
                  Grid.Column="0"
                  HorizontalAlignment="Stretch"
                  Margin="0"
                  Name="RecordCountGrid"
                  VerticalAlignment="Stretch">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="250" />
                    <ColumnDefinition Width="100" />
                </Grid.ColumnDefinitions>
                <sdk:Label Content="Maximum Number of Accounts to Retrieve:"
                           Height="28"
                           HorizontalAlignment="Right"
                           Margin="3"
                           Name="RetrieveAccountsLabel"
                           VerticalAlignment="Center" />
                <ComboBox Grid.Column="1"
                          Height="23"
                          HorizontalAlignment="Center"
                          Margin="0"
                          x:Name="NumberOfAccountsToRetrieve"
                          AutomationProperties.LabeledBy="{Binding ElementName=RetrieveAccountsLabel}" 
                          VerticalAlignment="Center"
                          Width="60">

                    <ComboBoxItem IsSelected="True"
                                  Content="100" />
                    <ComboBoxItem Content="300" />
                    <ComboBoxItem Content="600" />
                    <ComboBoxItem Content="900" />


                </ComboBox>
            </Grid>
        </Grid>
        <sdk:DataGrid AutoGenerateColumns="False"
                      HorizontalAlignment="Stretch"
                      Margin="5"
                      x:Name="AccountsGrid"
                      VerticalAlignment="Stretch">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="AccountName"
                                        Header="Account Name"
                                        Binding="{Binding Path=Name, Mode=OneTime}"
                                        IsReadOnly="True"
                                        Width="300" />
                <sdk:DataGridTextColumn x:Name="MainPhone"
                                        Header="Main Phone"
                                        Binding="{Binding Path=Telephone1, Mode=OneTime}"
                                        IsReadOnly="True"
                                        Width="*" />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>
        <!-- Message area -->
        <ScrollViewer x:Name="MessageArea"
                      Visibility="Collapsed"
                      Grid.Row="0"
                      Grid.Column="0">
            <StackPanel x:Name="Errors" />
        </ScrollViewer>
    </Grid>

</UserControl>

MainPage.xaml.cs

using System;
using System.Collections.ObjectModel;
using System.Data.Services.Client;
using System.Linq;
using System.Threading;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Crm.Sdk.Samples.CrmODataService;


namespace Microsoft.Crm.Sdk.Samples
{
 public partial class MainPage : UserControl
 {
  private SynchronizationContext _syncContext;
  private CrmContext _context;
  private String _serverUrl;
  public DataServiceCollection<Account> AccountCollection;

  public MainPage()
  {
   InitializeComponent();

   //Keeps a reference to the UI thread
   _syncContext = SynchronizationContext.Current;


   //Get the ServerUrl 
   _serverUrl = ServerUtility.GetServerUrl();



   if (!String.IsNullOrEmpty(_serverUrl))
   {

    //Setup Context
    _context = new CrmContext(
        new Uri(String.Format("{0}/xrmservices/2011/organizationdata.svc/",
            _serverUrl), UriKind.Absolute));

    //This is important because if the entity has new 
    //attributes added the code will fail.
    _context.IgnoreMissingProperties = true;

    // load the page with an initial 100 account records
    RetrieveAccounts(100);


   }
   else
   {
    String errorMessage = "Unable to access server url. Launch this Silverlight Web Resource from a CRM Form OR host it in a valid HTML Web Resource with a <script src='../ClientGlobalContext.js.aspx' type='text/javascript'></script>";
    showErrorDetails(new Exception(errorMessage));
    btnRetrieveAccounts.IsEnabled = false;
   }


  }

  private void btnRetrieveAccounts_Click(object sender, RoutedEventArgs e)
  {
   AccountCollection = null;
   ComboBoxItem selection = (ComboBoxItem)NumberOfAccountsToRetrieve.SelectedItem;
   switch ((String)selection.Content)
   {
    case "100":
     RetrieveAccounts(100);
     break;
    case "300":
     RetrieveAccounts(300);
     break;
    case "600":
     RetrieveAccounts(600);
     break;
    case "900":
     RetrieveAccounts(900);
     break;
   }
  }

  private void RetrieveAccounts(int number)
  {
   try
   {

    DataServiceQuery<Account> query = (DataServiceQuery<Account>)_context.AccountSet
     .AddQueryOption("$top", number)
     .AddQueryOption("$select", "Name,Telephone1");



    query.BeginExecute(ProcessPages<Account>, new PagingContext<Account>()
    {
     ServiceContext = _context,
     Query = query,
     PageProcessor = delegate(DataServiceCollection<Account> results)
     {
      try
      {

       if (null == AccountCollection)
       {
        AccountCollection = new DataServiceCollection<Account>(_context);
        AccountCollection.Load(results);
       }
       else
       {
        for (int i = 0; i < results.Count; i++)
        {
         AccountCollection.Add(results[i]);
        }
       }

       AccountsGrid.ItemsSource = AccountCollection;
      }
      catch (Exception ex)
      {
       _syncContext.Send(new SendOrPostCallback(showErrorDetails), ex);
      }

      return true;
     }
    });

   }
   catch (SystemException ex)
   {
    _syncContext.Send(new SendOrPostCallback(showErrorDetails), ex);
   }


  }

  private static void ProcessPages<T>(IAsyncResult result)
  {
   try
   {
    PagingContext<T> context = (PagingContext<T>)result.AsyncState;

    QueryOperationResponse<T> response;
    if (null == context.Query)
    {
     response = (QueryOperationResponse<T>)context.ServiceContext.EndExecute<T>(result);
    }
    else
    {
     response = (QueryOperationResponse<T>)context.Query.EndExecute(result);
     context.Query = null;
    }

    DataServiceCollection<T> results = new DataServiceCollection<T>(response);

    if (null != context.PageProcessor && !context.PageProcessor(results))
    {
     //Stop processing
     return;
    }

    DataServiceQueryContinuation<T> token = results.Continuation;
    if (null == token)
    {
     return;
    }

    context.ServiceContext.BeginExecute(token, ProcessPages<T>, context);
   }
   catch (Exception ex)
   {
    throw ex;
   }
  }

  public void showErrorDetails(object ex)
  {
   //Assure the control is visible
   this.MessageArea.Visibility = System.Windows.Visibility.Visible;

   this.AccountsGrid.Visibility = System.Windows.Visibility.Collapsed;


   Exception exception = (Exception)ex;
   String type = exception.GetType().ToString();

   this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = String.Format("{0} Message: {1}", type, exception.Message) });
   this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = String.Format("Stack: {0}", exception.StackTrace) });
   if (exception.InnerException != null)
   {
    String exceptType = exception.InnerException.GetType().ToString();
    this.Errors.Children.Add(new TextBlock() { TextWrapping = System.Windows.TextWrapping.Wrap, Text = String.Format("InnerException: {0} : {1}", exceptType, exception.InnerException.Message) });
   }
  }

 }

 sealed class PagingContext<T>
 {
  public DataServiceContext ServiceContext { get; set; }

  public DataServiceQuery<T> Query { get; set; }

  public Func<DataServiceCollection<T>, bool> PageProcessor { get; set; }
 }



}

ServerUtility.cs

using System;
using System.Windows.Browser;

namespace Microsoft.Crm.Sdk.Samples
{
 public static class ServerUtility
 {
  /// <summary>
  /// Returns the ServerUrl from Microsoft Dynamics CRM
  /// </summary>
  /// <returns>String representing the ServerUrl or String.Empty if not found.</returns>
  public static String GetServerUrl()
  {
   String serverUrl = String.Empty;

   //Try to get the ServerUrl from the Xrm.Page object
   serverUrl = GetServerUrlFromContext();

   return serverUrl;
  }

  /// <summary>
  /// Attempts to retrieve the ServerUrl from the Xrm.Page object
  /// </summary>
  /// <returns></returns>
  private static String GetServerUrlFromContext()
  {
   try
   {
    // If the Silverlight is in a form, this will get the server url
    ScriptObject xrm = (ScriptObject)HtmlPage.Window.GetProperty("Xrm");
    ScriptObject page = (ScriptObject)xrm.GetProperty("Page");
    ScriptObject pageContext = (ScriptObject)page.GetProperty("context");

    String serverUrl = (String)pageContext.Invoke("getServerUrl");

    return serverUrl;
   }
   catch
   {
    return String.Empty;
   }
  }
 }
}

sample_/RESTRetrieveMultipleSilverlightTestPage.html

This HTML Web resource was created from the RESTRetrieveMultipleSilverlightTestPage.html page created with the Silverlight application project. It has only been edited to replace the default <script src="Silverlight.js" type="text/javascript"></script> script reference with <script type="text/javascript" src="../ClientGlobalContext.js.aspx"></script> so that the Silverlight application will have access to the application context.

See Also

Tasks

Sample: Retrieve Multiple Records Using the REST Endpoint with JavaScript

Reference

Limitations on number of records returned

Concepts

Query Microsoft Dynamics CRM Data Using the REST Endpoint
Use the REST Endpoint with Silverlight Web Resources
Use the REST Endpoint for Web Resources

Microsoft Dynamics CRM 2011
Send comments about this topic to Microsoft.
© 2013 Microsoft Corporation. All rights reserved.