Export (0) Print
Expand All

Using the Client Object Model

The client-side object model (CSOM) provides client-side applications with access to a subset of the SharePoint Foundation server object model, including core objects such as site collections, sites, lists, and list items. As described in Data Access for Client Applications, the CSOM actually consists of three distinct APIs—the ECMAScript object model, the Silverlight client object model, and the .NET managed client object model—that target distinct client platforms. The ECMAScript object model and the Silverlight client object model provide a smaller subset of functionality. This is designed to enhance the user experience, because it minimize the time it takes Silverlight applications or JavaScript functions running in a Web page to load the files required for operation. The .NET managed client object model provides a larger subset of functionality for standalone client applications. However, these APIs provide a broadly similar developer experience and work in a similar way.

Ff798388.note(en-us,PandP.10).gifNote:
For more information about the CSOM, see Managed Client Object Model on MSDN. For examples of how to perform common tasks with the CSOM, see Common Programming Tasks on MSDN.

Of the three principal approaches to client-side data access, using the CSOM APIs is the only approach that provides the kind of hierarchical, strongly-typed representation of SharePoint objects, such as sites and Webs, that compares to server-side development. The CSOM is the only approach for any client-side data access scenarios beyond list queries. The CSOM allows you to query SharePoint lists by creating CAML queries. This is the most efficient way to query lists, although it requires that developers revert to creating CAML queries. Specific cases where you should favor the use of the CSOM include the following:

  • You need to perform advanced list operations, such as complicated joins or paging. You can also perform joins through REST requests, although this is subject to various limitations.
  • You need to manipulate SharePoint objects, such as sites or Webs.
  • You need client-side access to other areas of SharePoint functionality, such as security.

Query Efficiency and the Client Object Model

Although the CSOM APIs mirror the server APIs in terms of functionality, the way CSOM actually works necessitates some changes to the way in which you approach your development tasks. The CSOM uses a set of specialized Windows Communication Foundation (WCF) services to communicate with the SharePoint server. Each API is optimized to work efficiently with remote clients and with the asynchronous loading model used by the Ajax and Silverlight frameworks. This section describes how these efficiency mechanisms work and how you can use them to best effect in your client-side SharePoint applications.

Request Batching

All the CSOM APIs include a ClientContext class that manages the interaction between client-side application code and the SharePoint server. Before you perform any operations in client-side code, you must instantiate a ClientContext object with the URL of a SharePoint site, as shown by the following code example.

ClientContext clientContext = new ClientContext(webUrl);

The clientContext instance provides programmatic access to the objects within your site, such as the current Web object, the parent Site object, and a Lists collection. Communication with the server occurs when you call the ExecuteQuery method, or the ExecuteQueryAsync method, on the ClientContext instance. Consider the following example, which was adapted from the Client Reference Implementation.

Notice that the class names in the CSOM differ from their server-side counterparts in that they no longer have the SP prefix, like SPList or SPWeb—instead, they are simply List and Web.

private void GetParts(string searchSku)
{
  Parts.Clear();
  List partsList = clientContext.Web.Lists.GetByTitle("Parts");
  List inventoryLocationsList = 
    clientContext.Web.Lists.GetByTitle("Inventory Locations");

  CamlQuery camlQueryPartsList = new CamlQuery();
  camlQueryPartsList.ViewXml =
    @"<View>
        <Query>
          <Where>
            <BeginsWith>
              <FieldRef Name='SKU' />
              <Value Type='Text'>" + searchSku + @"</Value>
            </BeginsWith>
          </Where>
        </Query>
      </View>";

  CamlQuery camlQueryInvLocationList = new CamlQuery();
  camlQueryInvLocationList.ViewXml = 
    @"<View>
        <Query>
          <Where>
            <BeginsWith>
              <FieldRef Name='PartLookupSKU' />
              <Value Type='Lookup'>" + searchSku + @"</Value>
            </BeginsWith>
          </Where>
          <OrderBy Override='TRUE'>
            <FieldRef Name='PartLookupSKU' />
          </OrderBy>
        </Query>
        <ViewFields>
          <FieldRef Name='PartLookup' LookupId='TRUE' />
          <FieldRef Name='PartLookupSKU' />
          <FieldRef Name='PartLookupTitle' />
          <FieldRef Name='PartLookupDescription' />
          <FieldRef Name='BinNumber' />
          <FieldRef Name='Quantity' />
        </ViewFields>
        <ProjectedFields>
          <Field Name='PartLookupSKU' Type='Lookup' List='PartLookup'                    
                 ShowField='SKU' />
          <Field Name='PartLookupTitle' Type='Lookup' List='PartLookup' 
                 ShowField='Title' />
          <Field Name='PartLookupDescription' Type='Lookup' List='PartLookup' 
                 ShowField='PartsDescription' />
        </ProjectedFields>
        <Joins>
          <Join Type='LEFT' ListAlias='PartLookup'>
          <!--List Name: Parts-->
            <Eq>
              <FieldRef Name='PartLookup' RefType='ID' />
              <FieldRef List='PartLookup' Name='ID' />
            </Eq>
          </Join>
        </Joins>
      </View>";

  partListItems = partsList.GetItems(camlQueryPartsList);
  inventoryLocationListItems = 
    inventoryLocationsList.GetItems(camlQueryInvLocationList);
  
  clientContext.Load(partListItems);
  clientContext.Load(inventoryLocationListItems);
  clientContext.ExecuteQueryAsync(onQuerySucceeded, onQueryFailed);
}

The following actions take place within this code example:

  1. The client-side code uses the ClientContext class to define a series of operations to execute against a SharePoint site. In this example, the operations are the following:
    1. Retrieve the Parts list.
    2. Retrieve the Inventory Locations list.
    3. Build a query for the Parts list.
    4. Build a query for the Inventory Locations list.
    5. Execute the query against the Parts list.
    6. Execute the query against the Inventory Locations list.
    7. Load the Parts query results (which causes them to be returned to the client).
    8. Load the Inventory Locations query results.
  2. The client code calls the ClientContext.ExecuteQueryAsync method. This instructs the CSOM to send a request containing all operations to the server.
  3. The SharePoint server executes the series of operations in order and returns the results to the client.
  4. The CSOM notifies the client-side code of the results by invoking the callback method associated with the onQuerySucceed delegate.

This following illustration shows this process.

Client object model request batching

Ff798388.2ca2dddd-9342-4f28-ba36-12de06739654(en-us,PandP.10).png

This request batching process helps to improve performance and reduce network traffic in two ways. First, fewer Web service calls occur between the client and the SharePoint server, which reduces the "chattiness" of the client-server interface. For example, you can perform two list queries in a single request. Second, as a set of operations occur on the server in a single request, the data being acted on doesn't need to be moved between the client and the server for the intermediate operations—only the list of operations and the final result set are passed between the client and the server.

Request batching requires a different mindset when you create queries from client-side code. First, be aware that you do not have access to any results until you call ExecuteQueryAsync (or ExecuteQuery) and receive the call back with the results. If you need to implement conditional logic in the client-side code that can't be expressed in the command list that you send to the server, you will need to execute multiple queries. Second, you should aim to group your operations to minimize the number of service calls. This means you may need to think about how you sequence your logic in order to take full advantage of request batching.

List Queries with CAML

As you can see from the preceding code example, the CSOM supports querying list data using CAML. The CSOM allows you to submit any CAML queries that you could use with the SPQuery class, including the use of join statements and view projections. In fact, the CSOM is the only client-side data access mechanism that supports the use of joins with view projections that executes as a single list query.

Ff798388.note(en-us,PandP.10).gifNote:
The REST interface provides partial support for joins, when you specify implicit joins as part of the filter criteria. For more information, see Using the REST Interface. For an explanation of list joins and projections, see Data Access in SharePoint 2010.

The ability to use join statements and view projections in list queries leads to more efficient queries and reduces the number of queries required. The following example, which was adapted from the Client Reference Implementation, includes a CAML query that uses list joins and view projections. The query performs a left join between the Part Suppliers list and the Suppliers list. These lists are related by lookup columns, as shown in the following illustration.

Entity-relationship diagram

Ff798388.6de09e85-f66b-4485-bcca-9fd96a2a2e39(en-us,PandP.10).png

The query then uses a view projection to select the supplier name, DUNS, and rating that match a specified part SKU from the join table.

private void GetPartSuppliers()
{
  if (currentItem != null)
  {
    List partSuppliersList = clientContext.Web.Lists.GetByTitle("Part Suppliers");
    CamlQuery camlQuery = new CamlQuery();
    camlQuery.ViewXml =
      @"<View>
          <Query>
            <Where>
              <Eq>
                <FieldRef Name='PartLookup' LookupId='TRUE' />
                <Value Type='Lookup'>" + currentItem.Part.Id + @"</Value>
              </Eq>
            </Where>
          </Query>
          <ViewFields>
            <FieldRef Name='SupplierLookupTitle' />
            <FieldRef Name='SupplierLookupDUNS' />
            <FieldRef Name='SupplierLookupRating' />
          </ViewFields>
          <ProjectedFields>
            <Field Name='SupplierLookupTitle' Type='Lookup' 
                   List='SupplierLookup' ShowField='Title' />
            <Field Name='SupplierLookupDUNS' Type='Lookup' 
                   List='SupplierLookup' ShowField='DUNS' />
            <Field Name='SupplierLookupRating' Type='Lookup' 
                   List='SupplierLookup' ShowField='Rating' />
          </ProjectedFields>
          <Joins>
            <Join Type='LEFT' ListAlias='SupplierLookup'>
            <!--List Name: Suppliers-->
              <Eq>
                <FieldRef Name='SupplierLookup' RefType='ID' />
                <FieldRef List='SupplierLookup' Name='ID' />
              </Eq>
            </Join>
          </Joins>
        </View>";

    partSuppliersListItems = partSuppliersList.GetItems(camlQuery);
    clientContext.Load(partSuppliersListItems);

    //Get Supplier Data
    clientContext.ExecuteQueryAsync(onPartSupplierQuerySucceeded, onQueryFailed);
  }
}

In this example, the use of a list join dramatically improves the efficiency of the query and reduces network traffic. Without the list join, you would need to issue more queries and perform the join logic in your application code. The use of a view projection reduces the amount of data returned by the query, because it returns only a subset of field values that are relevant to your requirements. In the case of client-side data access, the benefits of this approach are even more pronounced. The ability to join lists in client-side data queries reduces the load on the server, reduces the number of round trips required between the client and the server, and reduces the overall amount of data transmitted between the client and the server.

The CSOM does not provide a mechanism for querying data across multiple lists that are not associated by a lookup field. In other words, there is no client-side functional equivalent of the SPSiteDataQuery class. If you need to perform a cross-list query from client-side code, consider creating a list view on the server that performs the list aggregation. You can then query the aggregated data from your client-side code.

Using LINQ for Objects

When you use the CSOM, you can write LINQ queries against client-side objects, such as lists and Webs, and then use the ClientContext class to submit these queries to the server. It's important to understand that when you take this approach, you are using LINQ to Objects to query SharePoint objects, not LINQ to SharePoint. This means that your LINQ expressions are not converted to CAML and you will not see the performance benefits that are associated with CAML conversion.

Ff798388.note(en-us,PandP.10).gifNote:
The LINQ to SharePoint provider is not supported by any of the client-side data access mechanisms.

Submitting a LINQ to Objects expression to the server reduces network traffic between the client and the server and alleviates some of the processing burden on the client platform. This is because you use the LINQ expression to narrow down the objects returned through server-side processing, instead of retrieving all objects in a collection, such as all Webs on a site and iterating through the collection on the client. LINQ to Objects makes it easy to specify fairly complex criteria with which to narrow down your result set. For example, you could use the following code to retrieve all non-hidden lists that have been created since March 20, 2010.

private IEnumerable<List> newLists;

var dt = new DateTime(2010, 3, 20);
var query = from list
            in clientContext.Web.Lists
            where list.Created > dt && list.Hidden == false
            select list;

newLists = clientContext.LoadQuery(query);
clientContext.ExecuteQueryAsync(onPartQuerySucceeded, onPartQueryFailed);

In-Place Load and Queryable Load

The client object model provides two different mechanisms for data retrieval—in-place load and queryable load.

  • In-place load. This loads an entire collection into the client context. To perform an in-place load, you use the ClientContext.Load method.
  • Queryable load. This returns an enumerable collection of results. To perform a queryable load, you use the ClientContext.LoadQuery method.

For example, the following code uses an in-place load to load the collection of lists in the context site into the client context object.

clientContext.Load(clientContext.Web.Lists);
clientContext.ExecuteQueryAsync(onQuerySucceeded, onQueryFailed);

After executing the query, you can access the list collection through the clientContext.Web.Lists property. When you perform an in-place load, the client context manages object identity for you. If you modify a setting such as the title of a list, and then you perform a second query that loads the same list, the client context understands that the returned items refer to the same list and it preserves the changes.

The following code uses an equivalent queryable load to load the collection of lists in the context site.

private IEnumerable<List> allLists;
 
var query = from list in clientContext.WebLists
            select list;

this.allLists = clientContext.LoadQuery(query);
clientContext.ExecuteQueryAsync(onQuerySucceeded, on QueryFailed);

When you use a queryable load, you are not loading items into the client context. Instead, you are loading items into a results array—in this case, the allLists field. In this case, object identity is not managed by the client context. If you were to repeat the query, the client context would simply repopulate the allLists field from server-side data and would overwrite any changes you had made on the client in the meantime.

In terms of performance, there are no advantages or disadvantages to either approach. Because you can only use an in-place load to load one collection of objects at a time, there are circumstances in which you may want to use a queryable load to simultaneously load an alternative view of the data on your site. For example, suppose you would like to add the completion date for every project within your organization into all the calendars on your SharePoint site. The projects are distributed across several custom lists. In this scenario, you would use the in-place load for the collection of calendar lists, because these are the objects that you want to update. You would use the queryable load for the collection of project lists, because these will not be updated.

Ff798388.note(en-us,PandP.10).gifNote:
For more information about using the CSOM to load object collections and query lists, see Data Retrieval Overview on MSDN.

Synchronous and Asynchronous Operations

As described earlier in this topic, the CSOM batches your client-side operations and sends them to the server for execution. The CSOM provides both a synchronous model and an asynchronous model for invoking this server-side execution. If you use the .NET managed client API or the Silverlight client API, you can use either approach. If you use the ECMAScript API, you must use the asynchronous approach. This prevents service calls from causing the Web browser to block user interaction for the duration of the call.

For example, suppose you are using the .NET managed client API or the Silverlight client API to retrieve data from the server:

  • If you call the ClientContext.ExecuteQuery method, your operation will be invoked synchronously. The thread that executes your code will wait for the server to respond before continuing.
  • If you call the ClientContext.ExecuteQueryAsync method, your operation will be invoked asynchronously. In this case, you specify callback methods to handle the server response, and the current thread remains unblocked.

Although the Silverlight client API supports the synchronous ExecuteQuery method, in most cases you will want to use ExecuteQueryAsync to submit your operation set. The following example, taken from the Client Reference Implementation, illustrates how you can use the ExecuteQueryAsync method with the Silverlight client API. The PartSearchButton_Click method executes when the user clicks a button in the Silverlight application.

private void PartSearchButton_Click(object sender, RoutedEventArgs e)
{
  bindingViewsModels.Clear();
  List partsList = clientContext.Web.Lists.GetByTitle("Parts");
            
  CamlQuery camlQueryPartsList = new CamlQuery();
  camlQueryPartsList.ViewXml = @"
    <View>
      <Query> 
       <Where>
         <BeginsWith>
           <FieldRef Name='SKU' />
           <Value Type='Text'>" + PartSkuTextBox.Text + @"</Value>
         </BeginsWith>
       </Where>
     </Query>
   </View>";

  partListItems = partsList.GetItems(camlQueryPartsList);
  clientContext.Load(partListItems);
  clientContext.ExecuteQueryAsync(onQuerySucceeded, onQueryFailed);
}

The ExecuteQueryAsync method accepts two arguments—a delegate for a method that is called if the server-side operation succeeds and a delegate for a method that is called if the server-side operation fails. If the operation is successful, the onQuerySucceeded method is called.

private void onQuerySucceeded(object sender, ClientRequestSucceededEventArgs args)
{
  this.Dispatcher.BeginInvoke(DisplayParts);
}

As you can see, this method also makes an asynchronous method call. The Dispatcher.BeginInvoke method invokes the DisplayParts method on the user interface (UI) thread. This is a mandatory approach when you work with Silverlight, because you must use the UI thread to execute logic that updates the UI. The DisplayParts method simply binds the query results to the appropriate UI controls. The following illustration shows this overall process.

Asynchronous execution with the Silverlight client API

Ff798388.1100252b-eb06-44b8-b470-6ccfddcc6b9f(en-us,PandP.10).png

In the previous example, what would happen if you called ExecuteQuery instead of ExecuteQueryAsync? Silverlight would throw an InvalidOperationException with the following message:

The method or property that is called may block the UI thread and it is not allowed. Please use background thread to invoke the method or property, for example, using System.Threading.ThreadPool.QueueUserWorkItem method to invoke the method or property.

In other words, Silverlight will not allow you to block the UI thread. To avoid this exception, you would need to execute the query on a background thread, as shown in the following code example.

private void PartSearchButton_Click(object sender, RoutedEventArgs e)
{
  bindingViewsModels.Clear();
  List partsList = clientContext.Web.Lists.GetByTitle("Parts");
  List inventoryLocationsList = 
    clientContext.Web.Lists.GetByTitle("Inventory Locations");
            
  CamlQuery camlQueryPartsList = new CamlQuery();
  camlQueryPartsList.ViewXml = @"
    <View>
      <Query>
        <Where>
          <BeginsWith>
            <FieldRef Name='SKU' />
            <Value Type='Text'>" + PartSkuTextBox.Text + @"</Value>
          </BeginsWith>
        </Where>
      </Query>
    </View>";

  partListItems = partsList.GetItems(camlQueryPartsList);
  clientContext.Load(partListItems);
  System.Threading.ThreadPool.QueueUserWorkItem(
    new WaitCallback(ThreadCallback), clientContext);
}

private void ThreadCallback(object s)
{
  var context = (ClientContext)s;
  context.ExecuteQuery();
  this.Dispatcher.BeginInvoke(DisplayParts);
}

In other words, if you don't use the ExecuteQueryAsync method, you must manually implement the asynchronous logic. Both methods are functionally correct. However, the ExecuteQueryAsync method makes your code easier to understand and is preferred from a stylistic perspective. The ExecuteQuery method is useful in applications where a synchronous execution model is appropriate, such as a command-line application or a PowerShell extension.

Accessing Binary Data

In some cases, you may want to either upload or download binary files, such as images or documents, from your Rich Interactive Application (RIA) or managed client. You can use the CSOM to access binary data like documents; however, there are varying levels of support, depending on the CSOM API that you use.

The managed version of the CSOM supports both binary file upload and download using File.OpenBinaryDirect and File.SaveBinaryDirect. The following example shows how to retrieve and save a picture using these APIs from a Windows Presentation Foundation (WPF) client. In this case, the method DisplayPix retrieves all the items in the Pix picture library and displays them to a WPF control in the call to AddPix. SavePix saves the file stream provided as an image to the Pix library, using the file name for the title.

private void DisplayPix()
{
  List pixList = clientContext.Web.Lists.GetByTitle("Pix");

  CamlQuery camlQuery = new CamlQuery();
  camlQuery.ViewXml = "<View/>";
  pictureItems = pixList.GetItems(camlQuery);

  clientContext.Load(pictureItems);
  clientContext.ExecuteQuery();

  foreach (ListItem pictureItem in pictureItems)
  {
    string fileRef = pictureItem["FileRef"].ToString();
    FileInformation fileInfo = File.OpenBinaryDirect(clientContext, fileRef);
    AddPix(fileInfo.Stream);
  }
}

private void SavePix(string filename, Stream stream)
{
  string url = "/sites/sharepointlist/Pix/" + filename;
  File.SaveBinaryDirect(clientContext, url, stream, true);
}

For a detailed example of using this API with the managed CLOM, see Using the SharePoint Foundation 2010 Managed Client Object Model with the Open XML SDK 2.0 on MSDN.

The Silverlight CSOM supports opening binary files from SharePoint with File.OpenBinaryDirect using the same syntax as the managed CSOM but does not support saving binary files. The European Computer Manufacturers Association (ECMA) script (JavaScript) CSOM does not support either saving or reading binary files from SharePoint.


Show:
© 2014 Microsoft