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.
[…] 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 […] […]
LikeLiked by 1 person
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…}
LikeLike
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/
LikeLike
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.”}}}”
LikeLike
DO not pass “X-RequestDigest”: fDigest value while using SP.RequestExecutor. it automaticaly fetches the Digest value and appends it to the request. Remove that value from Header
LikeLike
Oooh.. still 500 (Internal Server Error)
LikeLike
The default expiration time for the Request Digest is 30 mins, not 18 seconds.
LikeLike
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?.
LikeLike
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.
LikeLike
Thanks again, I think I misread 1800sec somewhere and assumed it 18sec as it is very short in my environment 😀
LikeLike
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.
LikeLike
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
LikeLike