Posted on:
Categories: Office 365;SharePoint
Description:

​Recently I was debugging a SharePoint-hosted add-in, which should be used in 'SharePoint online' and in 'SharePoint on-premises'. The initial situation was this: the add-in was working on 'SharePoint online' as expected, but when trying to run it on SharePoint on-premises, an error was displayed immediately. The error reported that the response received from the server was unexpected.

Because this error happened as soon as the add-in was trying to access the host web for the first time, I checked this part of the add-in's code first.

 

 

Let's see what this code is doing.

If you run a SharePoint add-in, SharePoint is providing some additional parameters via the URL. In my local development environment, the URL of the add-in looks like this:

http://app-af2e37d0f342ef.apps.domain.com/sites/ABAT/AP/Pages/Default.aspx?SPHostUrl=http://sp2013-ow/sites/ABAT&SPLanguage=en-US&SPClientTag=0&SPProductNumber=15.0.4797.1000&SPAppWebUrl=http://app-af2e37d0f342ef.apps.domain.com/sites/ABAT/AP

The above code is trying to extract the values for SPHostUrl and SPAppWeb from this URL.

"SPHostUrl" is the URL of the add-in's host web (the web that this add-in should be used in) and "SPAppWeb" is the URL of the internal web of this add-in (the web that add-ins are usually using to store their configuration data and that's not accessible to the common user).

As you can see, both URLs are very different, and they are pointing to different domains. The host URL starts with http://sp2013-ow and the app web URL starts with http://app-af1e37d0f372ef.apps.domain.com. That's intended because SharePoint add-ins should be separated from common SharePoint webs and that's done by providing a separate app domain (it's usually called domain separation). In SharePoint on-premises, this app domain is configurable via the Central Administration, in SharePoint online the app domain is configured upon setting up the tenant automatically.

If a SharePoint add-in needs to access its host web, it first needs to have proper permissions. Usually, this is done by adding the desired permissions to the AppManifest.xml file. The following image shows an example:

 

When using the add-in for the first time, a user needs to trust this add-in and while doing so, these permissions are granted to the add-in. But that's just the easy part. With proper permissions, the add-in is just allowed to access its host web, but some lines of code need to be added as well to finally enable the add-in to access the host web.

We have just learned that SharePoint add-ins are usually existing in a different domain. To be able to access the web which is existing outside of the add-in's app domain, the add-in needs to perform something which is called Cross-Domain Access.

Let's have a look back on the initial code. This code is trying to get access to the host web like this:

var hostWebContext = new SP.ClientContext(getRelativeUrlFromAbsolute(hostWebUrl));

The code takes the URL of the host web and creates a new ClientContext based on this URL. Although this does not take into account that we need to perform a Cross-Domain access, this line appears to be working in a SharePoint online environment. In other words: getting the host web object like this is working in SharePoint online, although it is not the recommended way of doing this:

var hostWeb = hostWebContext.get_web();

You can find some posts on the internet providing the above code as a sample. I don't want to speculate, why this is working in a SharePoint online environment and not in a SharePoint on-premises environment. Let' focus on how to do it the right way. Microsoft published a great article which is explaining the details. You can access this article by clicking on this link.

The recommended way how to access an add-in's host web is this:

// Get URL of the host web

var hostWebUrl = getQueryStringParameter("SPHostUrl");

 

// Get URL of the app web (web where the add-in is living)

appWebUrl = getQueryStringParameter("SPAppWebUrl");

 

// Get context of host web to be able to access it

context = new SP.ClientContext(appWebUrl);

var factory = new SP.ProxyWebRequestExecutorFactory(appWebUrl);

context.set_webRequestExecutorFactory(factory);

hostWebContextSite = new SP.AppContextSite(context, hostWebUrl);

The first two steps are almost the same: the URLs need to be extracted from the add-in's URL. The next step is that we need to create a new client context based on the URL of the add-in's web (not the host web!). To enable the Cross-Domain access, a new object of type ProxyWebRequestExecutorFactory needs to be created. This object will take care of handling the Cross-Domain access we are about to establish. To be able to create that object, another JavaScript library needs to be loaded first. This could be done within the content of the page or by manually loading the library.

This is how the library is loaded when using the first approach:

 

The additional JavaScript library (SP.RequestExecutor.js) is loaded by the last line in the above snippet.

This new RequestExecutor object is then passed to the client context by a call to set_webRequestExecutorFactory(). Now the client context can handle a Cross-domain access. Technically this is achieved by installing a local proxy object with is handling the cross-domain data transfer.

But there is an additional subject we need to take care of too. The way we need to access an object which is existing in the host web, changed also. Instead of using this code

var hostWeb = hostWebContext.get_web();

we now need to get a web object which is existing in the host web like this:

var oWeb = hostWebContextSite.get_web();

    hostWebContext.load(files);

    hostWebContext.executeQueryAsync(onSuccess, onFail);

As you can see the host web is accessed by using the hostWebContextSite object now – and not by using the context object directly.

If you are accessing an add-in's host web like this, your add-in will run on SharePoint on-line and SharePoint on-premises – and probably won't have any issues with Cross-Domain access anymore.

Before I close this blog post, here is another tip which usually helps me when debugging a SharePoint-hosted add-in. I usually change the onFail functions used by executeQueryAsync() like this:

function onFail(sender, args) {

        // Called if operation not has been successful

        ReasonWhyFailed = args.get_message();

        StackTrace = args.get_stackTrace();

     }

 

With this modification, you will get a better error description if something went wrong.