SharePoint Framework POST requests: watch out for OData version incompatibility

This week I've been building a SharePoint Framework web part that queries a Pages list, amongst other things. For reasons that I won't go into, I need to do this by sending a CAML query to the server. The usual way to do this is to send a POST request to the getitems REST endpoint:

[Web URL]/_api/web/lists/getbyid('[List GUID]')/getitems

Where the body of the request contains your CAML query:

'query': {
   '__metadata': {'type': 'SP.CamlQuery' },
   'ViewXml': '<View><Query><Where>...'
}

To do this the SPFx way, we leave jQuery.ajax behind and use the SPHttpClient.post method. However, when I did this, the server returned an HTTP 400/Bad Request response with the error message:

The property '__metadata' does not exist on type 'SP.CamlQuery'. Make sure to only use property names that are defined by the type.

After a bit of trial and error in Fiddler, I found the problem:

  • The SPHttpClient class appends an odata-version: 4.0 header to the request.
  • The SharePoint REST API, on my SharePoint Online tenancy at least, supports OData 3.0. (Send a GET request to /_api/$metadata.)
  • Manually removing the odata-version header from the request in Fiddler solves the problem.
Back in our web part code, fortunately, the SPHttpClient class provides a way of overriding headers. The post method accepts three arguments:
  • A string URL.
  • An SPHttpClientConfiguration value (at this stage there's only one to choose from).
  • An ISPHttpClientOptions object.
The ISPHttpClientOptions object holds the answer here. In addition to specifying our request body, we can use it to add or override headers:

const options: ISPHttpClientOptions = {
   headers: {'odata-version':'3.0'},
   body: {'query': {'__metadata': ...
};

Including the headers option ensures that the errant odata-version: 4.0 header is replaced by a more benign odata-version: 3.0 header, with the result that everything starts to work. Putting it all together, you get something like this (apologies for any dodgy TypeScript; we're all learning here):

public static getMyNews(contextIWebPartContextfiltersFilterValue)
   : Promise<NewsItem[]> {
        // Build a REST endpoint URL
        const restUrlstring = this.buildRestUrl(contextfilters);

        // Build a stringified request body
        const payloadstring = this.buildQueryPayload(filters);
        
        // Build an ISPHttpClientOptions object
        const optionsISPHttpClientOptions = {
            headers: {'odata-version':'3.0'},
            body: payload
        };

        // Send the request and parse the response
        return new Promise<NewsItem[]> 
        ((resolve: (optionsNewsItem[]) => voidreject: (errorany=> void=> {
            context.spHttpClient
                .post(restUrlSPHttpClient.configurations.v1options)
                .then((responseSPHttpClientResponse=> {
                    response.json().then((itemsany=> {
                        const newsItemsNewsItem[] = items.value.map(item => {
                            return<NewsItem> ({
                                id: item.GUID,
                                title: item.Title,
                                description: item.NewsItemSummary,
                                imageUrl: item.PublishingRollupImage
                            })
                        })
                        resolve(newsItems);
                    });
                });
        });
    }

Edit 3/3/17: A similar issue is known to affect calls to the Search REST API (again, it's a POST request) - see this thread for details.


Comments

  1. Hi Jason,
    Thanks for the post, helped me in getting CAML queries work via SPFx in general, but how do you get the PublishingRollupImage for the item? I've defined it in the URL $select=PublishingRollupImage, as well as in the CAML ViewFields, still it is not included in the returned item properties? I can get other fields just fine, such as Title, FileRef, and Created...

    Any tips?

    ReplyDelete
    Replies
    1. Hi Jussi,
      Yes, the publishing images are tricky/annoying. You can get the image fields on individual items from the FieldValuesAsHtml endpoint:

      .../_api/web/lists/getbytitle('Pages')/items(i)/FieldValuesAsHtml

      But that's not ideal, as you end up having to send a bunch of requests instead of just one. Also the PublishingPageImage and PublishingRollupImage return an img element rather than just a pure field value.

      I ended up adding my own simple URL fields to my page layout content type for the images I want to pull through to web parts.

      Delete

Post a Comment

Popular posts from this blog

Server-side activities have been updated

The target principal name is incorrect. Cannot generate SSPI context.

Custom Workflow Activity for Creating a SharePoint Site