At Work 01 : Two reasons I do not to use $.ajax with SharePoint REST calls

The first thing I really like about SharePoint REST APIs is, we do not need any reference to client assemblies or JS libraries. The basic operations in REST API includes CRUD operations on lists. You can find the list of these end points on MSDN. And we can find so many examples on MSDN and other websites using REST APIs with jQuery $.ajax.

Here is why I’m not a big fan of $.ajax with SharePoint APIs, for two reasons.

Reason 1 (POST requests):

I’ve used $.ajax for adding a list item and I thought it will be working fine.

var AddListItem = function(product) {
    return $.Deferred(function (def) {
     var data = {
                __metadata: { type: 'SP.Data.ProductsListItem' },
                Title: product.ProductName,
                ProductNumber: product.ProductNumber,
                Price: product.Price
     };
    $.ajax({
        url: webUrl + "/_api/web/lists/getbytitle('Products')/items",
        type: 'POST',
        contentType: 'application/json;odata=verbose',
        data: JSON.stringify(data),
        headers: {
            'Accept': 'application/json;odata=verbose',
            'X-RequestDigest': $('#__REQUESTDIGEST').val()
        },
        success: function (data) {
            def.resolve(data)
        },
        error: function (s,a,errMsg) {
            def.reject(errMsg)
        }
    });
  });
};

But I see a problem with POST requests. we need to include X-RequestDigest parameter in request and the Request digest value expires in 18 seconds(AFAIK). The code worked fine in my office365 environment. But not worked when I make a POST call after 18 seconds. The RequestDigest timeout was very less in one of my on-premise environments so the code was never able to make POST operations. returned error message like 403/401. So I removed $.ajax and used SharePoint cross domain JS library sp.requestexecutor.js to make REST calls.

    var AddListItem = function (product) {
        var executor = new SP.RequestExecutor(webUrl);
        return $.Deferred(function (def) {
            var url = webUrl + ";/_api/web/lists/getbytitle('Products')/items";
            var data = {
                __metadata: { type: 'SP.Data.ProductsListItem' },
                Title: product.ProductName,
                ProductNumber: product.ProductNumber,
                Price: product.Price
            };
            executor.executeAsync({
                url: url,
                method: 'POST',
                body: JSON.stringify(data),
                headers: {
                   'Accept': 'application/json; odata=verbose',
                    'content-type': 'application/json;odata=verbose';
                },
                success: function(data){def.resolve(data)},
                error: function(s,a,errMsg){def.reject(errMsg)}
            });
        });
    };

Now, this code looks same like the code we use to make REST calls in Cloud hosted apps and it is same except the URL and url in SP.RequestExecutor() Constructor. The URL in cloud hosted apps looks like

appweburl +"/_api/SP.AppContextSite(@target)/web/lists/getbytitle('Products')/items?@target=" +hostweburl

We are not using the key Object of cross domain API “SP.AppContextSite” here and the URL is same as we use with $.ajax. The second Difference is we are creating ‘SP.RequestExecutor()’ to current site URL(not any appWebUrl). The benefit here we get is we need not to pass X-RequestDigest parameter. This cross domain library will take care of getting the Latest Request Digest from server(https://site/_api/contextInfo) and append it to Request. This code worked perfectly in all of my environments (Office 365 and OnPremise). You need to load /_layouts/15/sp.requestexecutor.js‘ to use this library.

Reason 2 (making Social API calls):

In one of my projects we implemented a site collection which have list of community sub sites and you can follow / unfollow those communities from a WebPart at site collection level. And some of those communities are Private(with unique permissions). Assume you are using $.ajax to get the follow status / to follow / to unfollow.  The code to get the status of Follow looks like.

var followingManagerEndpoint = webUrl + "/_api/social.following";// you can use rootSite Url here
function isFollowed() {
    $.ajax( {
        url: followingManagerEndpoint + "/isfollowed",
        type: "POST",
        data: JSON.stringify( {
            "actor": {
                "__metadata": {
                    "type":"SP.Social.SocialActorInfo"
                },
                "ActorType":SP.Social.SocialActorType.site,
                "ContentUri":webUrl,
                "Id":null
            }
        }),
        headers: {
            "accept":"application/json;odata=verbose",
            "content-type":"application/json;odata=verbose",
            "X-RequestDigest":$("#__REQUESTDIGEST").val()
        },
        success: function (responseData) {
            stringData = JSON.stringify(responseData);
            jsonObject = JSON.parse(stringData);
            if (jsonObject.d.IsFollowed === true ) {
                alert('The user is currently following the site.');
                //stopFollowSite();
            }
            else {
                alert('The user is currently NOT following the site.');
                //followSite();
            }
        },
        error: requestFailed
    });
}

Assume that you are making this call to get the follow status of a private to which you do not have access. You cannot make a call with url as “webUrl + “/_api/social.following” because you do not have access to webUrl. So you can try rootSiteUrl in followingManagerEndPoint and pass the subsiteurl in contentUri parameter. It still thrown me an error(403 or 401, do not remember). The solution is  to get rid of $.ajax and use SP.RequestExecutor(rootSiteUrl).

var LoadIsFollowed = function (documentOrSiteUrl, rootSiteUrl, actortype, successcallback, failcallback) {
        var requestinfo = {
            url: rootSiteUrl+ '/_api/social.following/isfollowed',
            method: "POST",
            body: JSON.stringify({
                "actor": {
                    "__metadata": {
                        "type": "SP.Social.SocialActorInfo"
                    },
                    "ActorType": actortype,
                    "ContentUri": documentOrSiteUrl,
                    "Id": null
                }
            }),
            headers: {
                "accept": "application/json;odata=verbose",
                "content-type": "application/json;odata=verbose"
            },
            success: function (responseData) {
                successcallback(responseData);
            },
            error: function (s, a, errMsg) {
                failcallback(s, a, errMsg);
            }
        };
            var executor = new SP.RequestExecutor(rootSiteUrl);
            executor.executeAsync(requestinfo);
    };

Similarly we can implement code to follow/unfollow the subsite even you do not have access to it.

Advertisement

12 thoughts on “At Work 01 : Two reasons I do not to use $.ajax with SharePoint REST calls

  1. […] Two reasons not to use the $.ajax with SharePoint REST calls The first thing I really like about SharePoint REST APIs is, we do not need any reference to client assemblies or JS libraries. The basic operations in REST API includes CRUD operations on lists. You can find the list of these end points on MSDN. And we can find so many examples on MSDN and other websites using […] […]

    Liked by 1 person

  2. Im currently using this codes :

    app.factory(‘shptService’, [‘$rootScope’, ‘$http’,
    function ($rootScope, $http) {
    var shptService = {};

    //utility function to get parameter from query string
    shptService.getQueryStringParameter = function (urlParameterKey) {
    var params = document.URL.split(‘?’)[1].split(‘&’);
    var strParams = ”;
    for (var i = 0; i < params.length; i = i + 1) {
    var singleParam = params[i].split('=');
    if (singleParam[0] == urlParameterKey)
    return singleParam[1];
    }
    }
    shptService.appWebUrl = decodeURIComponent(shptService.getQueryStringParameter('SPAppWebUrl')).split('#')[0];
    shptService.hostWebUrl = decodeURIComponent(shptService.getQueryStringParameter('SPHostUrl')).split('#')[0];

    //form digest opertions since we aren't using SharePoint MasterPage
    var formDigest = null;
    shptService.ensureFormDigest = function (callback) {
    if (formDigest != null)
    callback(formDigest);
    else {
    $http.post(shptService.appWebUrl + '/_api/contextinfo?$select=FormDigestValue', {}, {
    headers: {
    'Accept': 'application/json; odata=verbose',
    'Content-Type': 'application/json; odata=verbose'
    }
    }).success(function (d) {
    formDigest = d.d.GetContextWebInformation.FormDigestValue;
    callback(formDigest);
    }).error(function (er) {
    alert('Error getting form digest value');
    });
    }
    };

    <

    //test isffollowed
    shptService.test_follow = function () {

    shptService.ensureFormDigest(function (fDigest) {

    var executor = new SP.RequestExecutor(shptService.hostWebUrl);

    var siteUrl = “https://domain.sharepoint.com/site1/”;

    executor.executeAsync({
    url: shptService.hostWebUrl + “/_api/social.following/isfollowed”,
    method: “POST”,
    body: JSON.stringify({
    “actor”: {
    “__metadata”: {
    “type”: “SP.Social.SocialActorInfo”
    },
    “ActorType”: 2,
    “ContentUri”: siteUrl,
    “Id”: null
    }
    }),
    headers: {
    “accept”: “application/json;odata=verbose”,
    “content-type”: “application/json;odata=verbose”,
    “X-RequestDigest”: fDigest
    },
    success: function (responseData) {

    console.log(“Success”);
    stringData = JSON.stringify(responseData);
    jsonObject = JSON.parse(stringData);

    if (jsonObject.d.IsFollowed === true) {
    alert(‘The user is currently following the site.’);
    }
    else {
    alert(‘The user is currently NOT following the site.’);

    }
    },
    error: function (err) {
    console.log(“Failed :(“);
    console.log(err);
    }
    });

    });

    };

    and
    im having this kind of response:

    Fri Sep 11 2015 17:14:37 GMT+0800 (Taipei Standard Time):RequestExecutor.PostMessage.Message: {“command”:”Query”,”url”:”https://alphasysgroup.sharepoint.com/sites/development/sharepointkanbo/_api/social.following/isfollowed”,”method”:”POST”,”body”:”{\”actor\”:{\”__metadata\”:{\”type\”:\”SP.Social.SocialActorInfo\”},\”ActorType\”:2,\”ContentUri\”:\”https://alphasysgroup.sharepoint.com/site1/\”,\”Id\”:null}}”,”headers”:{“accept”:”application/json;odata=verbose”,”content-type”:”application/json;odata=verbose”,”X-RequestDigest”:”0xDC04A5A89F1E9DAA76250F5CA4045A78BA8E64315A921A0D9921B26FBEEA63D3A58F057ABDF518289056210C05B9BF6178134CF8598C16BB2784C87263205B9E,11 Sep 2015 09:09:40 -0000″},”postMessageId”:”SP.RequestExecutor4″,”timeout”:90000}
    sp.requestexecutor.js:2 Fri Sep 11 2015 17:14:37 GMT+0800 (Taipei Standard Time):RequestExecutor.PostMessage.Target: https://alphasysgroup.sharepoint.com/sites/development/_layouts/15/AppWebProxy.aspx
    AppWebProxy.aspx?SP.AppPageUrl=https://alphasysgroup-9ec47d087c6b14.sharepoint.com/sites/developmen…:182 ErrorPage.OnMessage: Origin=https://alphasysgroup-9ec47d087c6b14.sharepoint.com, Data={“command”:”Query”,”url”:”https://alphasysgroup.sharepoint.com/sites/development/sharepointkanbo/_api/social.following/isfollowed”,”method”:”POST”,”body”:”{\”actor\”:{\”__metadata\”:{\”type\”:\”SP.Social.SocialActorInfo\”},\”ActorType\”:2,\”ContentUri\”:\”https://alphasysgroup.sharepoint.com/site1/\”,\”Id\”:null}}”,”headers”:{“accept”:”application/json;odata=verbose”,”content-type”:”application/json;odata=verbose”,”X-RequestDigest”:”0xDC04A5A89F1E9DAA76250F5CA4045A78BA8E64315A921A0D9921B26FBEEA63D3A58F057ABDF518289056210C05B9BF6178134CF8598C16BB2784C87263205B9E,11 Sep 2015 09:09:40 -0000″},”postMessageId”:”SP.RequestExecutor4″,”timeout”:90000}
    AppWebProxy.aspx?SP.AppPageUrl=https://alphasysgroup-9ec47d087c6b14.sharepoint.com/sites/developmen…:212 ErrorPage.PostMessage: Origin=https://alphasysgroup-9ec47d087c6b14.sharepoint.com, Data={“command”:”Query”,”postMessageId”:”SP.RequestExecutor4″,”responseAvailable”:false,”errorCode”:-1007,”errorMessage”:”Correlation ID: 80dd2c9d-8003-2000-2122-cf61cc7c22c9″}
    sp.requestexecutor.js:2 Fri Sep 11 2015 17:14:37 GMT+0800 (Taipei Standard Time):RequestExecutor.OnMessage
    sp.requestexecutor.js:2 Fri Sep 11 2015 17:14:37 GMT+0800 (Taipei Standard Time):RequestExecutor.OnMessage: Message.data={“command”:”Query”,”postMessageId”:”SP.RequestExecutor4″,”responseAvailable”:false,”errorCode”:-1007,”errorMessage”:”Correlation ID: 80dd2c9d-8003-2000-2122-cf61cc7c22c9″}
    sp.requestexecutor.js:2 Fri Sep 11 2015 17:14:37 GMT+0800 (Taipei Standard Time):RequestExecutor.OnMessage: Message.origin=https://alphasysgroup.sharepoint.com
    alphaKanban.js:263 Failed 😦
    alphaKanban.js:264 SP.ResponseInfo {responseAvailable: false, body: “”, statusCode: undefined, statusText: undefined, contentType: undefined…}

    Like

  3. create RequestExecutor object for appWebUrl not hostweburl
    var executor = new SP.RequestExecutor(shptService.appWebUrl); //use appWebUrl not host weburl
    url: shptService.hostWebUrl + “/_api/social.following/isfollowed”, //use appWebUrl here also

    make sure ContentUri is correct
    “ContentUri”: siteUrl, // use shptService.hostWebUrl if you are trying to getisFollowed of hostweb
    looks like “https://alphasysgroup.sharepoint.com/site1/” is not a valid site collection, It might be https://alphasysgroup.sharepoint.com/sites/site1/

    Like

  4. Done Implementing it Now having this Error:

    body: “{“error”:{“code”:”6, Microsoft.Office.Server.Social.SPSocialException”,”message”:{“lang”:”en-US”,”value”:”The operation failed because an internal error occurred. Internal type name: Microsoft.Office.Server.UserProfiles.FollowedContentException. Internal error code: 11.”}}}”

    Like

    • Even I think 18 seconds is too short. I never faced issue in my O365 site, but somehow it is too short in my onprem environment, not even 18 seconds. Do you know where to change this timeout value?.

      Like

      • Not sure where to change the value, but again it’s 30 MINUTES (1800 seconds). The token can be reused within its duration and it updates with every page refresh too. However if you’ve built a single page application or something where someone might stay on the same page for a long time doing asynchronous operations you can have a window.timeout function to refresh the token in the background and update the token element on the page if desired. Andrew Connell has some good examples of this in one of his Pluralsight courses.

        Like

  5. hi shekar, i am trying to do update list item and add list item using smae approach similar to what you mentioned in Client Part in a SharePoint hosted app. Unfortunately no luck. I tried many methods, CMOS script, $.ajax, RequestExecutor approach. but non of those worked. However, all approaches are working when i am keeping the code in content editor webpart.

    what so special in client part? i saw that when we keep code in client part, it is adding in iframe.

    please help me out. my eamil rajkumar.bathula@gmail.com would like to share my code if you ned.

    Like

  6. Hey Shekar.
    Great article. It helped me alot! I have been searching the web for answers on how to fix the ‘403 Forbidden error’ that i was getting constantly. Using the RequestExecutor and your example was exactly the solution that I was searching for.
    Thanks!
    Kind regards,
    Mugo

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s