OData in SharePoint 2013 with jQuery

This is the third part of a series of posts and assumes knowledge of RESTful Services in jQuery and Understanding OData.
In the previous post I showed an example of calling an OData service in the browser using the URI: http://server/_api/web/lists?$filter=startsWith(Title, 'Calendar')&$select=Hidden. This is an example of calling the SharePoint 2013 OData web service or as it commonly known: the ‘_api’ (underscore A.P.I). The _api is, in fact, just an alias (or mapping) to a service that was already present in SharePoint 2010: _vti_bin/client.svc. Calling this service directly wasn’t supported in SharePoint 2010 (it wasn’t OData or REST compliant until now) but if you have had any experience coding against the Client Side Object Model (CSOM) then you have been calling this service indirectly. It is important that you always use the _api alias as Microsoft provides no guarantee that the service end point won’t be moved or renamed in future releases. Side note: The SP2010 asmx web services are deprecated in SP2013 and although ListData.svc is not officially deprecated the new _api should be used going forward.

SP OData service diagram
SP OData service diagram – Thanks to Ted Pattison’s SPC12 slide deck for this image

Thankfully Microsoft decided that OData support would be useful and I think that the vast majority of developers will now rarely use the CSOM when writing SharePoint Apps or other SharePoint interfacing applications (at least from web clients; it may still have a place in desktop applications). The service API has been extended to cover:

  • Search
  • User Profiles
  • BCS
  • Taxonomy
  • Workflow
  • Publishing
  • More… (including analytics, feeds, IRM and e-discovery)

All the new functionality that is available via the RESTful interface is all present via the CSOM as well, however, the inter-platform and familiar nature of a RESTful service greatly reduces the learning curve for developers compared to the CSOM.

So, now that you have an understanding of REST and OData, the challenge of efficiently leveraging the _api service comes down to learning the service ‘syntax’. That is; learning the resource paths and query options that will return the data (SharePoint assets) which you require. To begin with you need to select an access point dependent on what you are looking for.

Service Access Points

  • Site http://server/site/_api/site
  • Web http://server/site/_api/web
  • User Profile http:// server/site/_api/SP.UserProfiles.PeopleManager
  • Search http:// server/site/_api/search
  • Publishing http:// server/site/_api/publishing

From an access point you can traverse objects using the same hierarchy which you will be familiar with having coded against the server-side API. For example /_api/web/lists/GetByTitle('Calendar')/fields/getbyid('<guid>') would return information about a single field on the Calendar list. The ‘methods’ which the SharePoint OData service use (eg GetByTitle and GetById) are extensions to the OData protocol to make the service more usable. If you have experience with the CSOM you can leverage this knowledge as the OData service can be called using syntax that is very similar. A good reference on this can be found here.

Rather that going into great depth regarding the _api vocabulary I will present practical code examples to perform GET, ADD, DELETE and UPDATE actions. Once you have these examples up and running you can look elsewhere for the syntax to find specific objects if it hasn’t been covered here.

Debugging Tip
Before you start debugging these examples keep in mind that if you press F5 to test your project it will attach the Visual Studio debugger and prevent the browser debugger from being allowed to attach. Instead press CNTL+F5.

GET Example

function onGetCustomer(customerId) {
 
  // begin work to call across network
  var requestUri = _spPageContextInfo.webAbsoluteUrl +
                "/_api/Web/Lists/getByTitle('Customers')/items(" + customerId + ")";
 
  // execute AJAX request
  $.ajax({
    url: requestUri,
    type: "GET",
    headers: { "ACCEPT": "application/json;odata=verbose" },
    success: function(data){
      alert(data.d.FirstName);
    },
    error:  function(){
      alert("Failed to get customer");
    }
  });
}

The _spPageContextInfo JavaScript object has been around since SharePoint 2007. It provides really easy way to start building the correct _api URI, as shown. It also includes a property for the current user Id among other values. The ACCEPT header is required when performing service call against the _api. I’ve explained the purpose of the ACCEPT header in a previous post but the point of note here is that the odata=verbose appended to the end is required in order for the service call to succeed. This is because the OData specification also supports odata=light (the default) but the _api doesn’t (as yet).

ADD Example

function onAddCustomer() {

    var requestUri = _spPageContextInfo.webAbsoluteUrl +
              "/_api/Web/Lists/getByTitle('Customers')/items/";

    var requestHeaders = {
      "ACCEPT": "application/json;odata=verbose",
      "X-RequestDigest": $("#__REQUESTDIGEST").val(),
    }

    var customerData = {
      __metadata: { "type": "SP.Data.CustomersListItem" },
      FirstName: "Paul"
    };

    requestBody = JSON.stringify(customerData);

    $.ajax({
      url: requestUri,
      type: "Post",
      contentType: "application/json;odata=verbose",
      headers: requestHeaders,
      data: requestBody,
      success: function(){
        alert("Created customer");
      },
      error: function(){
        alert("Failed to create customer");
      }
    });
  }
}

Explaining the form digest beyond “a security feature of ASP.NET” is outside of the scope of this article. The important thing to understand is that you must send the form digest along with any POST request or nothing will get updated. Luckily for us, the form digest is stored as the value of a well known element on the page: the element with id ‘__REQUESTDIGEST’ (the double underscore is not a typo). It must be passed as the value of the X-RequestDigest header, as shown.
This post is specifically about calling the _api with jQuery, however I will mention that in order to get the form digest in another context you would need to first perform a call to _api/contextinfo where it can be retrieved.

When adding a new entity you must provide the __metadata 'type' property with the list item entity type of the list. You can think of this like the content type of the list. The name of the list item entity type can often be predicted as it’s built based on the list name. E.g If you have a list called Customers then it will have list item entity type of “SP.Data.CustomersListItem”. However, this quickly ceases to hold true; If a second Customers list were to be created then the list item entity type for it would be different (an integer gets inserted). As such, it is bad practice to hard code this value and instead it should be the result of previous service call. However for the sake of code brevity I haven’t heeded my own warning in this example. In order to obtain the list item entity type you will need to perform a GET query using the resource path to the list with a URI like /_api/Web/Lists/getByTitle('Customers') and then inspect the ListItemEntityTypeFullName property of the resulting data object.

DELETE Example

function onDeleteCustomer(customerId) {

  var requestUri = _spPageContextInfo.webAbsoluteUrl +
                "/_api/Web/Lists/getByTitle('Customers')/items(" + customerId + ")";

  var requestHeaders = {
    "ACCEPT": "application/json;odata=verbose",
    "X-RequestDigest": $("#__REQUESTDIGEST").val(),
    "If-Match": "*" // delete the item even if another user has updated it since we last fetched it
  }

  $.ajax({
    url: requestUri,
    type: "DELETE",
    contentType: "application/json;odata=verbose",
    headers: requestHeaders,
    success: function(){
      alert("Deleted customer");
    },
    error: function(){
      alert("Failed to delete customer");
    }
  });

}

SharePoint supports an optimistic concurrency scheme which is what prevents users overwriting one-another’s changes when working concurrently. It works by only updating items which at the same version as when you last fetched it. This means that if another user updates the item before you can submit your changes, then your update will fail rather than wiping the other user’s changes. The If-Match header must define the eTag (version) which can be updated by the call. The “*” allows us to ignore the scheme and just overwrite no matter what. This is okay in this scenario only because we are performing a delete action. When performing an update you will need to query for the latest eTag first.

UPDATE Example

function onUpdateCustomer(customerId, firstName) {
  // first we need to fetch the eTag to ensure we have the most recent value for it
  var requestUri = _spPageContextInfo.webAbsoluteUrl +
                "/_api/Web/Lists/getByTitle('Customers')/items(" + customerId + ")";
 
  // execute AJAX request
  $.ajax({
    url: requestUri,
    type: "GET",
    headers: { "ACCEPT": "application/json;odata=verbose" },
    success: function(data){
      // got the eTag
      var etag = data.d.etag;
  
      var requestUri = _spPageContextInfo.webAbsoluteUrl +
                "/_api/Web/Lists/getByTitle('Customers')/items(" + customerId + ")";
      
      // set the MERGE verb so we only need to provide the update delta
      var requestHeaders = {
        "ACCEPT": "application/json;odata=verbose",
        "X-RequestDigest": $("#__REQUESTDIGEST").val(),
        "X-HTTP-Method": "MERGE",
        "If-Match": etag
      }
  
      var customerData = {
        __metadata: { "type": "SP.Data.CustomersListItem" },
        FirstName: firstName
      };
  
      requestBody = JSON.stringify(customerData);
  
      $.ajax({
        url: requestUri,
        type: "POST",
        contentType: "application/json;odata=verbose",
        headers: requestHeaders,
        data: requestBody,
        success: function(data){
          alert("Updated customer");
        },
        error: function(data){
          alert("Failed to update customer");
        }
      });
    },
    error:  function(data){
      alert("Failed to get customer eTag");
    }
  });
}

Note how the eTag is obtained and set appropriately.

The OData specification provides two verbs for performing updates: MERGE and PUT. Using PUT is impractical as replaces the target object with the object representation that it receives. This means that any properties that are not provided in the representation will be set to null. MERGE on the other hand only updates the properties that are present on the received object representation. This more efficiently utilises bandwidth and is easier to code. However, since MERGE is not one of the verbs that is defined in the HTTP specification, using the MERGE verb may not flow through network intermediaries as seamlessly as methods that are defined in HTTP. As such, the X-HTTP-Method header is set appropriately and we perform a POST. Conceptually, this should be thought of a MERGE rather than a POST operation.

A final note
These examples assume that your code is running in the context of a SharePoint App and is only accessing data in the App Web (or alternatively is running on a page deployed as part of a full trust solution). Should you wish to use the _api in a SharePoint App to access assets outside of the App Web you will need an OAuth enabled solution in order to authenticate beyond the scope of the App. I will undoubtedly blog about this in the future. Please subscribe to my feed to catch that post when it’s written.

Understanding OData

“The Open Data Protocol (OData) is a Web protocol for querying and updating data”

as defined on the OData site here. From a web developer’s point of view, I think it can be better defined as such:

‘A standardized syntax for RESTful web services’

 
As such, if you don’t have solid understanding of RESTful services and calling RESTful services with jQuery then I suggest you read the previous post in this series which addresses that topic specifically. I have also written a blog on OData in SharePoint 2013 specifically which assumes the knowledge laid out here.
 
OData protocol – agnostic of implementation

OData logo
OData logo

OData defines a standard syntax which defines how resource representations (entities) are queried via a RESTful URI: [the base URI (eg http://server/service.svc)] + [the resource path (eg /departments)] + [the query options(eg ?$select=name)]. A standardised approach makes the service easier to work with for anyone with previous OData experience. The resource path can be determined as it is structured logically and the query options, once learned, are repeatable across services.

The resource path is used to locate a collection of entities, a single entity or a single property that the service surfaces in a hierarchical manner. E.g.

  • http://server/service.svc/departments – collection of departments
  • http://server/service.svc/departments(1) – single department
  • http://server/service.svc/organisation – single organisation
  • http://server/service.svc/organisation/name – name of the organisation

It’s worth keeping in mind that the OData protocol is fundamentally similar to SQL as it aims to achieve the same goal of data access. It is the query options that allow us to perform a subset of SQL-like queries: select ($select), where ($filter), order by ($orderby), join ($expand) and paging ($top and $skip) operations. E.g.

  • http://server/service.svc/departments?$select=name – returns only the name element for each department
  • http://server/service.svc/departments?$filter=name eq HR – returns only the HR department element
  • http://server/service.svc/departments?$orderby=name – returns all department elements sorted by name
  • http://server/service.svc/departments?$expand=directors – returns the department elements and including the department director
  • http://server/service.svc/departments?$skip=40&top=20 – returns the third page of 20 results

Multiple query options can be used at once (?$filter=staffCount gt 100&$select=name) to create complex queries. Note that a dollar sign ‘$’ character is prepended to each of OData query option parameters.

OData compliant service being consumed directly in IE
OData compliant service being consumed directly in IE

The filter query option may also support a range of math, string and date operations. It is important to remember, however, that an OData service has no obligation to support all (or any) query options.

$filter logical and arithmetic functions
Using the OData $filter query parameter – Thanks to Ted Pattison’s SPC12 slide deck for this image
$filter string functions
Using the OData $filter query parameter – Thanks to Ted Pattison’s SPC12 slide deck for this image
$filter date and math functions
Using the OData $filter query parameter – Thanks to Ted Pattison’s SPC12 slide deck for this image

Fundamentally, that is OData. The specification has a lot more to it but if you are comfortable with it at this level then it is just a matter or reading up on the spec. The blog post here expands a little on the fundamentals I’ve just described or the full spec can found here but it’s a rather difficult read as it’s raw unstyled HTML.
 
Please see the next post in this series on OData in SharePoint 2013 with jQuery.