Cross Domain and SharePoint Hosted Apps using CSOM


Continuing on from last week blog post on Cross Domain and SharePoint Hosted Apps using REST, today I’m going to show you how to do the same Cross Domain calls but by using CSOM.

If you remember the 2 different calls I made in my project were a call back to the host domain to retrieve the title, and to retrieve all the lists in the host web. Here I’m going to extend that project and include two additional buttons that are going to do exactly the same thing, but using CSOM.

Below is a screen shot where we last left off.

If you were following along, open the solution from last week, and let us continue.

  • Open the Default.aspx page. Here we will add the two buttons to the relevant sections. Under the last button with the id=”btnStandardRestGetTitle” but before the </div> lets add the button to get Title via CSOM.
<span><input type="button" id="btnCSOMGetTitle" value="Get Title via CSOM" onclick="getTitleCSOM()" /></span>
  • While we are in the Default.aspx page, let us add the button to get the lists via CSOM as well. Under the last button with the id=”btnStandardRestGetLists” but before the </div> lets add the button to get Lists via CSOM.
<span><input type="button" id="btnCSOMGetLists" value="Get Lists via CSOM" onclick="getListsCSOM()" /></span>
  • Open up your App.js file. Let us add the two click event handlers for the buttons we just placed on default.aspx
/*Button Click Get CSOM*/
function getTitleCSOM() {
    execCSOMTitleRequest();
}
/*button Click Get List CSOM*/
function getListsCSOM() { execCSOMListRequest();}
  • Lastly we are going to add the two functions that returns the title and Lists, with their corresponding Success and Fail handlers.
/*********************************CSOM*****************************************************/
//CSOM Cross Domain call to obtain HostWeb Title
function execCSOMTitleRequest() {
    var context;
    var factory;
    var appContextSite;
    var collList;
    //Get the client context of the AppWebUrl
    context = new SP.ClientContext(appwebUrl);
    //Get the ProxyWebRequestExecutorFactory
    factory = new SP.ProxyWebRequestExecutorFactory(appwebUrl);
    //Assign the factory to the client context.
    context.set_webRequestExecutorFactory(factory);
    //Get the app context of the Host Web using the client context of the Application.
    appContextSite = new SP.AppContextSite(context, hostwebUrl);
    //Get the Web
    this.web = appContextSite.get_web();
    //Load Web.
    context.load(this.web);
    context.executeQueryAsync(
        Function.createDelegate(this, successTitleHandlerCSOM),
        Function.createDelegate(this, errorTitleHandlerCSOM)
        );
    //success Title
    function successTitleHandlerCSOM(data) {
        $('#lblResultTitle').html("<b>Via CSOM the title is:</b> " + this.web.get_title());
    }
    //Error Title
    function errorTitleHandlerCSOM(data, errorCode, errorMessage) {
        $('#lblResultLists').html("Could not complete CSOM call: " + errorMessage);
    }
}
//CSOM Cross domain call to obtain HostWeb Lists
function execCSOMListRequest(){
    var context;
    var factory;
    var appContextSite;
    var collList;
    //Get the client context of the AppWebUrl
    context = new SP.ClientContext(appwebUrl);
    //Get the ProxyWebRequestExecutorFactory
    factory = new SP.ProxyWebRequestExecutorFactory(appwebUrl);
    //Assign the factory to the client context.
    context.set_webRequestExecutorFactory(factory);
    //Get the app context of the Host Web using the client context of the Application.
    appContextSite = new SP.AppContextSite(context, hostwebUrl);
    //Get the Web
    this.web = appContextSite.get_web();
    // Get the Web lists.
    collList = this.web.get_lists();
    //Load Lists.
    context.load(collList);
    context.executeQueryAsync(
        Function.createDelegate(this, successListHandlerCSOM),
        Function.createDelegate(this, errorListHandlerCSOM)
        );
    //Success Lists
    function successListHandlerCSOM() {
            var listEnumerator = collList.getEnumerator();
            $('#lblResultLists').html("<b>Via CSOM the lists are:</b><br/>");
        while (listEnumerator.moveNext()) {
            var oList = listEnumerator.get_current();
            $('#lblResultLists').append(oList.get_title() + " (" + oList.get_itemCount() + ")<br/>");
        }
    }
    //Error Lists
    function errorListHandlerCSOM(data, errorCode, errorMessage) {
        $('#lblResultLists').html("Could not complete CSOM Call: " + errorMessage);
    }
};
  • If we now run out solution, you should see all three ways of obtaining the title and lists from the host.

  • By clicking Get Title via CSOM this will return the title “Cannonfodder Development” in my case, exactly the same result as if we tried via REST.

  • By clicking Get Lists via CSOM this will return all the lists from my host Cannonfodder development, exactly the same list as if we tried via REST.

I hope these two blogs gave you a basic understanding of the different ways you can interact with the host in SharePoint 2013.

Cross Domain and SharePoint Hosted Apps using REST


When building SharePoint Apps, because they sit in a different Domain to your SharePoint website there are “blocking mechanisms” that prevents it from retrieving data from the SharePoint website. To be able to communicate, you will need to use the Cross Domain library. The cross domain library is a JavaScript file known as SP.RequesterExecutor.js, which is referenced by the SharePoint app, and hosted in the SharePoint website. You can find the SP.RequesterExecutor.js file under the “_layouts/15/” directory.

In SharePoint 2013, REST (Representational State Transfer) is a great way of communicating with SharePoint websites. You can test out REST directly in the browser. For example, in your browser type:

http://<site>/_api/web/?$select=Title


In the above REST call, I have requested the Web and got the Title only. If I didn’t add $select=Title I would have retrieved all information about dev.cannonfodder.local web. For more information on to how to use REST it can be found at the following Microsoft website http://msdn.microsoft.com/en-gb/library/fp142385.aspx

Cannot see feeds?

If when you did the above REST query you didn’t see the XML feed, but received the following message, just follow these steps.

  • Access Internet Options. Then on the Content tab, click Settings.

  • In the Feed and Web Slice Settings untick Turn on feed reading view. Click OK, OK and then refresh your browser with the REST query in it. You should now be seeing the data similar to my initial screenshot.

Cross Domain project

I’m going to walk you through building a SharePoint Hosted App, that will make 2 different Cross Domain calls. One will obtain the Title of the Host website, and the second will obtain all the lists in the host site.

  • Open Visual Studio and create a new App for SharePoint 2013 project.
  • Point it to your development site, and set the hosting to SharePoint Hosted.
  • First open the AppManifest.xml file, and on the Permission tab, give the scope of Web the permission of Read.
  • On your default.aspx page, within the PlaceHolderMain ContentPlaceHolder put the following.
    <div>
    <h2>Please select a way to bring back the title from the host web:</h2>
    </div>
    <div><input id="btnCrossDomainGetTitle" onclick="getTitleXd()" type="button" value="Get Title via REST Cross Domain" /></div>
    <div>
    <h2>Please select a way to bring back all lists in the host web:</h2>
    </div>
    <div><input id="btnCrossDomainGetLists" onclick="getListsXd()" type="button" value="Get Lists via REST Cross Domain" /></div>
    
  • Now switch to the App.js file.
  • First thing we need to do is set up the page to load the SP.RequestExecutor.js script. We also need to obtain the SPHostUrl.
    //global variables.
    var hostwebUrl
    var appwebUrl;
    var web;
    // This code runs when the DOM is ready and creates a context object which is needed to use the SharePoint object model
    /*Get the page ready*/
    $(document).ready(function () {
        hostwebUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));
        appwebUrl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));
        var scriptbase = hostwebUrl + "/_layouts/15/";
        $.getScript(scriptbase + "SP.RequestExecutor.js");
    });
    function getQueryStringParameter(paramToRetrieve) {
        var params = document.URL.split("?")[1].split("&");
        var strParams = "";
        for (var i = 0; i &amp;lt; params.length; i = i + 1) {
            var singleParam = params[i].split("=");
            if (singleParam[0] == paramToRetrieve)
                return singleParam[1];
        }
    }
     
  • Now we need to add the button click handler, and the code to call and display the Title.
    /*Button Click Get Title Cross Domain*/
    function getTitleXd() {
         execCrossDomainTitleRequest();
    }
    //Cross Domain Call to obtain HostWeb Title.
    function execCrossDomainTitleRequest() {
        var executor;
        executor = new SP.RequestExecutor(appwebUrl);
        var url = appwebUrl + "/_api/SP.AppContextSite(@target)/web/Title?@target='" + hostwebUrl + "'";
        executor.executeAsync({
            url: url,
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: successTitleHandlerXD,
            error: errorTitleHandlerXD
        }
        );
    }
    //Success Title
    function successTitleHandlerXD(data) {
        var jsonObject = JSON.parse(data.body);
        $('#lblResultTitle').html"<b>Via Cross Domain the title is:</b> " + jsonObject.d.Title);
    }
    //Error with Title.
    function errorTitleHandlerXD(data, errorCode, errorMessage) {
        $('#lblResultTitle').html("Could not complete cross-domain call: " + errorMessage);
    }
    
  • Now let’s add the button click handler and the code to display the lists from the host web.
/*Button Click Get Lists Cross Domain*/
function getListsXd() { execCrossDomainListRequest(); }
//Cross Domain Call to obtain Host Web Lists
function execCrossDomainListRequest() {
    var executor;
    executor = new SP.RequestExecutor(appwebUrl);
    var url = appwebUrl + "/_api/SP.AppContextSite(@target)/web/lists?@target='" + hostwebUrl + "'";
    executor.executeAsync({
        url: url,
        method: "GET",
        headers: {"Accept" : "application/json; odata=verbose"},
        success: successListHandlerXD,
        error: errorListHandlerXD
    });
}
//Success Lists
function successListHandlerXD(data){
    var jsonObject = JSON.parse(data.body);
    //Get LIsts
    var lists = jsonObject.d.results;
    $('#lblResultLists').html("<"b>Via Cross Domain the lists are:</b>");
    //Loop through each item adding to the label.
    var listsHtml = $.each(lists, function(index, list){
        $('#lblResultLists').append(list.Title + " (" + list.ItemCount + ")
");
    });
}
//Error Lists
function errorListHandlerXD(data, errorCode, errorMessage){
    $('#lblResultLists').html("Could not complete cross-domain call: " + errorMessage);
}
  • Now let’s run the app and see it working.

Cross domain call without using SP.RequestExecutor.js

Now during my learning journey of Cross Domain, I discovered that I can still call into my host site, using a REST URL based on the App web URL. I don’t believe this is a supported way, as I haven’t found anything on Microsoft sites that confirm this, however I have got this working On-Prem and Autohosted in 365.

  • Back on our app, open up the Default.aspx file again.
  • Add the two following buttons, put the btnStandardRestGetTitle after the btnCrossDomainGetTitle button, but before the </div>. And put the btnStandardRestGetLists after the btnCrossDomainGetLists, but before the </div>

<input id="btnStandardRestGetTitle" onclick="getTitle()" type="button" value="Get Title standard REST" />

<input id="btnStandardRestGetLists" onclick="getLists()" type="button" value="Get Lists standard REST" />

  • Back in the App.js file. First we need to add a function that gets the actual path of the application Url.
//Obtains the path upto the actual application. E.g. http://app123.app.code/SubSite/CrossDomainApp
//gets http://app123.app.com/SubSite
function getUrlPath() {
    var webRel = _spPageContextInfo.webAbsoluteUrl;
    var lastIndex = webRel.lastIndexOf('/');
    var urlpath = webRel.substring(0, lastIndex);
    return urlpath;
}
  • Now we need to add the button click handler, and the code to call and display the Title.
/*Button Click Get Title Rest*/
function getTitle() {
    execRESTTitleRequest();
}
//REST Call to obtain HostWeb Title
function execRESTTitleRequest() {
    var url = getUrlPath() + "/_api/web/?$select=Title";
    $.ajax({
        url: url,
        method: "GET",
        headers: { "Accept": "application/json; odata=verbose" },
        success: successTitleHandler,
        error: errorTitleHandler
    });
}
//Success Title
function successTitleHandler(data) {
    $('#lblResultTitle').html("<b>Via REST the title is:</b>" + data.d.Title);
}
//Error Title
function errorTitleHandler(data, errorCode, errorMessage) {
    $('#lblResultTitle').html("Could not complete REST call: " + errorMessage);
}
  • Now let’s add the button click handler and the code to display the lists from the host web.
/*Button Click Get List Rest*/
function getLists() { execRESTListRequest(); }
//REST Call to obtain HostWeb Lists
function execRESTListRequest() {
    var url = getUrlPath() + "/_api/web/Lists";
    $.ajax({
        url: url,
        method: "GET",
        headers: { "Accept": "application/json; odata=verbose" },
        success: successListHandler,
        error: errorListHandler
    });
}
//Success List
function successListHandler(data) {
    var lists = data.d.results;
    $('#lblResultLists').html("<b>Via REST the lists are:</b><br/>");
    var listsHtml = $.each(lists, function (index, list) {
        $('#lblResultLists').append(list.Title + " (" + list.ItemCount + ")<br/>");
    });
}
//Error Lists
function errorListHandler(data, errorCode, errorMessage) {
    $('#lblResultLists').html("Could not complete REST call: " + errorMessage);
}
  • Now let’s run the App

You have learnt two ways of doing a Cross Domain call using REST. One way is the correct way recommend by Microsoft. The other way is a way I found, but I’m unsure if this is a valid way. It works and sometimes that’s all that matters.