Using REST Services with a Provider Hosted App in SharePoint 2013

Today's topic seems straightforward enough, right? There's lots of documentation all over the interwebs about how to do this so should be a piece of cake. Well, as it turns out, when I did this a couple of weeks ago I found that there is a lot of missing information, misleading information, and in some cases incorrect information out there about how to do this. So I'm going to describe here the main points of how to get this to work and try and clear up a few gaps and what I call "tribal knowledge" about what is needed to make this work.

Let's start by considering the scenario - you want to use a provider-hosted app to connect to SharePoint and retrieve data via search. Now, by definition a provider hosted app is going to exist in a different domain from your SharePoint site where you are going to execute the query. This introduces the first problem: there are several docs out there that show REST calls being made using jQuery. Guess what - that's not going to work. Why not? Because your app is in a different domain from your SharePoint site, jQuery will fail by default with a "no transport" error. Steve's Seemingly Random Yet Useful Tip #1 - don't bother configuring the cross domain property of your AJAX query with jQuery; it will still fail when you get over to SharePoint land.

Okay, so we determine that we're in a different domain, then that must mean that we need to use the cross domain library that SharePoint offers in order to make this query work, right? So you follow the instructions to modify your AppManifest.xml, you change your javascript to use the SP.RequestExecutor object, and yet your request fails again. What's the problem here? Well, you can't use the host web Url with SP.RequestExecutor, you must use an App Web. But wait, provider-hosted apps don't have App Webs, so what gives? Well, the fact of the matter is you need to have your provider-hosted app create an App Web so that you can hook it up with the SP.RequestExecutor object. Steve's Seemingly Random Yet Useful Tip #2 - add an artifact to your SharePoint Application (not the web application that is your provider-hosted web app) that will force an App Web to be created. In my case I just added a custom site column. I don't do anything with it, I don't refer to it ever again, I just add it to the project, and that forces an App Web to be created. That allows me to use this tiny piece of code to get the App Web Url and pass it to my SP.RequestExecutor constructor: var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));

There's also one other thing worth noting here. When you want to use the cross domain library, you need to modify the AppManifest.xml by changing this:

  <AppPrincipal>
<RemoteWebApplication ClientId="*" />
</AppPrincipal>
 

To this:

<AppPrincipal>
<Internal AllowedRemoteHostUrl="~remoteAppUrl"/>
</AppPrincipal>

Well guess what happens when you do that? If you also want to use CSOM in your code behind with a high-trust app, you will no longer be able to do so. Your app won't have the clientID it requires to make the connection to your SPTrustedSecurityTokenIssuer, so all of your CSOM calls will fail with authentication errors. Steve's Seemingly Random Yet Useful Tip #3 - it really does you no good in this case to make those changes to your AppManifest.xml to add support for the cross domain library; you're better off just leaving it as is so you can also use CSOM and server-side code if needed.

Okay, so now we're getting somewhere. The last important thing to understand here though is that when you go to run your code, you are going to have a very sub-optimal user experience by default. What do I mean by that? Well when your code executes, your REST call is going to a different domain. What you should expect to happen is that the end user then will get prompted for their credentials, even when using Windows auth. This of course is a terrible thing to have happen - a user launches an app, they see a blank page and suddenly it's asking for their Windows username and password. Not only is it a jarring experience, but if you're paranoid at all, the last thing you're going to do is give your credentials when a blank browser window randomly pops up in front of you. Steve's Seemingly Random Yet Useful Tip #4 - (this assumes Windows auth because it's simpler for now) - remember that your REST call is going to be processed by your App Web; to avoid those authentication prompts, add *.yourAppWebDomain.com to your list of Intranet Sites in IE. That stops the authentication prompts from occurring and you get a seamless end to end user experience.

Finally, here's an example of what my javascript actually ended up looking like to make the call to my app web from my provider-hosted app:

 

function getSearchInfo()
{
    try
    {
        //use the app web for the search endpoint
        var appweburl = decodeURIComponent(getQueryStringParameter("SPAppWebUrl"));

 

        //create the Url for getting the info, needs to look like this:
        //https://sp5.vbtoys.com/_api/search/query?querytext='Author:"Steve+Peschka"'
        var searchHost = appweburl + "/_api/search/query";

 

        //create the query terms
        var qry = "'Author:\"" + $("#authorName").html() + "\"'";

 

        //create the concatenated query url
        var qryUrl = searchHost + "?querytext=" + qry;

 

        //initialize the RequestExecutor with the app web Url
        var executor = new SP.RequestExecutor(appweburl);

 

        //issue the query
        executor.executeAsync(
            {
                url: qryUrl,
                method: "GET",
                headers: { "Accept": "application/json; odata=verbose" },
                success: onSearchSuccess,
                error: onSearchError
            }
        );

 

    } catch (e)
    {
        //do something here
    }
}