Wednesday, 7 November 2012

Access end-user data (in the host web) from a SharePoint 2013 app

Continuing my series on developing SP2013 apps, in this article we’ll look at the special approaches needed to access data in the host web – meaning the regular lists/libraries the user interacts with outside of any created by your app. This is something that some apps will need to do, but others may not. For some, the whole premise around what the app does will be working with collaboration data in team sites/My Sites etc., so these techniques will be crucial there. Because there are so many flavors of SharePoint data access in apps, I’m going to spend some time explaining which this article applies to – feel free to scroll ahead to the code samples though!

Whilst this article series focuses on SharePoint-hosted apps some of the code/techniques I describe here may also work in other app scenarios, such as a provider-hosted app (e.g. where the app is implemented as a .NET site hosted on another server). At the time of writing (November 2012), the MSDN samples are *extremely* confusing as to which approach can be used in which scenario – I discuss this somewhat below, but essentially I will add more detail to this post as Office 365 and “provider-hosted” scenarios become clearer to me. For now, the scenario that all the samples below have been tested on is an on-premise SharePoint-hosted app which accesses data in the host web.

Here’s the evolving table of contents for this article series:

  1. SharePoint 2013 apps – architecture, capability and UX considerations
  2. Getting started – creating lists, content types, fields etc. within a SharePoint app (provisioning)
  3. Working with data in the app web, and why you should
  4. Access end-user data (in the host web) from a SharePoint 2013 app [this article]
  5. Rolling out SharePoint 2013 apps to the enterprise - tenant scope and PowerShell installs
  6. Azure is the new SharePoint ‘_layouts’ directory
  7. “Host web apps” – provisioning files (e.g. master pages) to the host web
  8. “Host web apps” – provisioning fields and content types
  9. Deploying SP2013 provider-hosted apps/Remote Event Receivers to Azure Websites (for Office 365 apps)
  10. Working with web parts within a SharePoint app

Before we launch into things, if you’re unsure what I mean by “host web” and “app web”, then Working with the app web, and why you should goes into some detail, but here’s a quick reminder:–

  • Host web - the regular team site where our users access their documents etc., and where the app is installed
  • App web - the “isolated” subweb which gets created to house any SharePoint artifacts the app creates (such as pages, lists etc.), and which the user is typically directed to when they enter the app 

Flavors of remote data access in apps

As you wade through MSDN samples and articles like this one, my recommendation is to understand which “flavor” of task you’re trying to accomplish, and what the article is discussing. The permutations are mind-boggling, and lead to much confusion on the journey to understanding apps – here are some examples:

App type

Remote data access

SharePoint-hosted Page in app web accessing host web data using the cross-domain library and REST
SharePoint-hosted Page in app web accessing host web data using JavaScript CSOM
Auto-hosted (Azure)/
provider-hosted/O365 app
Remote HTML page which uses cross-domain library to access data in app web
Auto-hosted (Azure)/
provider-hosted/O365 app
Remote HTML page which JavaScript CSOM to access data in app web
Auto-hosted (Azure)/
provider-hosted/O365 app
Remote HTML page which uses cross-domain library to access data in host web (maybe – haven’t seen this done yet)
Auto-hosted (Azure)/
provider-hosted/O365 app
Remote HTML page which crafts a standard AJAX request to host web or other SharePoint site, adding an OAuth token to the ‘Authorization’ header
Auto-hosted (Azure)/
provider-hosted/O365 app
Remote .NET code (e.g. web service, scheduled task etc.) which uses the .NET CSOM to request data in host web or other SharePoint site (using TokenHelper class to pass OAuth token)

Did I miss any? And note these are just accessing SharePoint list data – there are more scenarios around say, accessing SQL data in Azure/other locations.

The italicized ones are what I’m focusing on in this article. Interestingly, I notice the 3rd and 4th flavors are covered in some MSDN samples (see here and here respectively), so the techniques I’m writing about here should have broader usage than just my first two flavors. Confusingly however, those samples only appear to work when the provider-hosted remote site is running on http://localhost, and there are no notes to explain this – if you configure it as a bona-fide, deployable IIS website then the code stops working (and other things start to become required such as the X-UA-Compatible metatag). My guess is that browser/JavaScript security checks aren’t performed with localhost (it knows it isn’t really remote), and when this isn’t the case then “high-trust” app configuration is required for on-premise scenarios (this involves specifying the app client ID, using Register-SPAppPrincipal and so on). I’ll confirm this in the future.

One notable omission is “accessing data in the app web from the host web” (i.e. the reverse direction) – an example of this could be a web part within a team site which “reaches into” an app to read/write some data. The scenario sounds kinda useful to me, but I suspected the app model would not allow it. My tests showed that this is NOT possible, and there are blocking mechanisms in place – you’ll get a "Sorry, this site hasn't been shared with you" error message when you execute your remote call.

When and why – using the JavaScript cross-domain library (SP.RequestExecutor)

If you’ve done development with the SharePoint client object model (CSOM) before, you might be asking why we need a special JavaScript library to access some data from an app – surely we just pass the URL to the SP.ClientContext object right? Well no, this doesn’t always work. The answer is around cross-site scripting – or more correctly, the protection that browsers have to prevent cross-site scripting attacks. This is centered on the ‘same-origin’ policy used by JavaScript/web browsers, which specifies that some client code can only access data in the same URL domain – otherwise websites could effectively hack each other and the internet would be chaos.

However, in many app SharePoint app scenarios, you might want to access data which isn’t on the same URL domain as your code (typically JavaScript within a web page). So for example, if you’re following best practice and your SharePoint apps run on a separate URL domain (e.g. http://[identifier].cob-apps.dev compared to http://team.cob.dev in my test VM) then this will be an issue for you.

The cross-domain library is designed to simplify these JavaScript scenarios – particularly by removing the need to work with OAuth tokens.

How it works

So if JavaScript requests are usually prohibited from talking across URL domains, how is the cross-domain library able to do it? In short, the technique used is to dynamically create an IFrame on to a page in the host web – by default this is an out-of-the-box application page called AppWebProxy.aspx, though it’s also possible to use your own. This effectively turns the “remote” call into local call by accessing it on a host web URL such as http://team.cob.dev/_layouts/15/AppWebProxy.aspx  – the JavaScript code which talks to the SharePoint lists executes there, and from here it’s possible for the code to read the value displayed in the IFrame. Although the JavaScript is somewhat obfuscated since Microsoft generated it with Script# (like the rest of the CSOM), you can see some details of the implementation in SP.RequestExecutor.js:

SP.RequestExecutor 

Context - how I use all this in my “learner” time—tracking app

Before we get to the examples, for folks who are vaguely following this article series here’s how I’m using the cross-domain library in the dummy app I’m building. My app mainly stores it’s data in the app web, but just for fun there is a list in the host web which stores the “target hours” a user must log that week:

UtililsationTargetsList

I use the library to query this list for the current user, then display it within my app:

TimeTracking_SummaryUnderTarget 

Examples

1. Get title, URL or other properties of host web (_api/REST):

In this first example, I’ll try to cover some fundamentals for working with the REST service. (N.B. This is async/AJAX programming in JavaScript - if you haven’t done much of this before, you might need another reference to get you started. Check out Using the JavaScript Client OM and jQuery to work with lists). Essentially we build a REST URL (which we can test in the browser address bar), and then specify whether we want our data returned as JSON or XML. Most samples tend to use JSON, but I’ll show how to get XML later on - JSON does often make sense though, because it works so well with JavaScript/jQuery. So, we then  use some boilerplate async code to get a JSON object from the string returned by the service. As we work through these examples, the key thing to note is the data we asked for will always be in the jsonObject.d property – this will make sense as you see more of these samples, but it’s crucial to understand when you’re writing your own code:

var hostweburl;
var appweburl;
 
function sharePointReady() {
    // retrieve passed app web/host web URLs..
    hostweburl = decodeURIComponent($.getUrlVar("SPHostUrl"));
    appweburl = decodeURIComponent($.getUrlVar("SPAppWebUrl"));
 
    loadDependentScripts();
}
 
function loadDependentScripts() {
    var scriptbase = hostweburl + "/_layouts/15/";
 
    // Load the js files and continue to the successHandler
    $.getScript(scriptbase + "SP.Runtime.js",
        function () {
            $.getScript(scriptbase + "SP.js",
                function () { $.getScript(scriptbase + "SP.RequestExecutor.js", getAllHostWebPropsUsingREST); }
                );
        }
    );
}
 
function getAllHostWebPropsUsingREST() {
    var executor;
 
    // although we're fetching data from the host web, SP.RequestExecutor gets initialized with the app web URL..
    executor = new SP.RequestExecutor(appweburl);
    executor.executeAsync(
        {
            url:
                appweburl +
                "/_api/SP.AppContextSite(@target)/web/?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: onGetAllHostWebPropsUsingRESTSuccess,
            error: onGetAllHostWebPropsUsingRESTFail
        }
    );
}
 
function onGetAllHostWebPropsUsingRESTSuccess(data) {
    var jsonObject = JSON.parse(data.body);
    // note that here our jsonObject.d object (representing the web) has ALL properties because 
    // we did not select specific ones in our REST URL. However, we're just displaying the web title here..
    $('#hostWebTitle').text(jsonObject.d.Title);
}
 
function onGetAllHostWebPropsUsingRESTFail(data, errorCode, errorMessage) {
    alert('Failed to get host site. Error:' + errorMessage);
}
 
// jQuery plugin for fetching querystring parameters..
jQuery.extend({
    getUrlVars: function () {
        var vars = [], hash;
        var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
        for (var i = 0; i < hashes.length; i++) {
            hash = hashes[i].split('=');
            vars.push(hash[0]);
            vars[hash[0]] = hash[1];
        }
        return vars;
    },
    getUrlVar: function (name) {
        return jQuery.getUrlVars()[name];
    }
});

So, given that our REST request asked for the host web and all it’s properties, if I use the IE Dev Tools script debugger to pause on the jsonObject.d property, I see some recognizable properties of the web:

AnalyzingJSONObject_WebProps

In the code sample above, you can see that I ask for the “Title” property and put it in a DIV on the page with the following line:

$('#hostWebTitle').text(jsonObject.d.Title);

And to no surprise, this is what it looks like on the screen:

WebProps_Output

Just on a related note, if you only need one property (or a couple) from the web, it’s far more performant and efficient to only ask the server for these properties (since less data is downloaded to the client). To do this, the REST URL should change to something like (asking for the Title only here):

appweburl + "/_api/SP.AppContextSite(@target)/web/Title?@target='" + hostweburl + "'"

2. Querying for lists in the host web (_api/REST)

In this sample we ask for all the lists in the host web – for each one, we list out the title and the current number of items. The change to the REST part is simple (we use “/web/lists" as the URL), but once we have the data a little thought is needed – we have a collection of items here, and so some iteration is needed. Therefore, in my success handler I’m using jQuery’s each method with a callback which uses the Title and ItemCount properties.

Note that this is a cut-down sample:

// omitted for brevity: variable declarations, setup code to load dependent scripts, get hostweburl from querystring etc. (see previous example for this)
function getHostWebListsUsingREST() {
    var executor;
 
    // although we're fetching data from the host web, SP.RequestExecutor gets initialized with the app web URL..
    executor = new SP.RequestExecutor(appweburl);
    executor.executeAsync(
        {
            url:
                appweburl +
                "/_api/SP.AppContextSite(@target)/web/lists/?@target='" + hostweburl + "'",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: onGetHostWebListsUsingRESTSuccess,
            error: onGetHostWebListsUsingRESTFail
        }
    );
}
 
function onGetHostWebListsUsingRESTSuccess(data) {
    var jsonObject = JSON.parse(data.body);
    var lists = jsonObject.d.results;
    var listsHtml = $.each(lists, function (index, list) {
        $('#lists').append(list.Title + "(" + list.ItemCount + ")<br />");
    });
}
 
function onGetHostWebListsUsingRESTFail(data, errorCode, errorMessage) {
    alert('Failed to get host site. Error:' + errorMessage);
}

If I look at the data returned in a JavaScript debugger, I can see my collection:

AnalyzingJSONObject

..and on the page, I see my lists with their respective item counts:

ListsAndItemCounts_Output

3. Querying for specific items in a specific list in the host web (_api/REST)

This sample is my ‘real’ code in my time-tracking app - it's used in the screenshot displaying "target hours" that I showed earlier. Here, I do the usual setup work to ensure scripts are loaded first. Then, in this case, I’m querying for records belonging to the current user – so I need an initial CSOM request to find out who this is, and then once I have the username I can proceed with the main query. Because of this 2-step process I'll show the full code for this sample. As in the previous examples, I then use SP.RequestExecutor to call over to the host web and get my data - effectively I build up a REST URL with the details of the list I want to query/column I want to filter on, and pass this to RequestExecutor.executeAsync() – my data is then returned in JSON format:

var context;
var user;
var hostweburl;
var appweburl;
 
var utilTargetsList = "Utilisation%20targets";
var utilTargetsListFriendlyName = "Utilisation targets";
 
var userName = "Chris OBrien";
var fetchedUserName;
var targetHoursResult;
 
// This function is executed after the DOM is ready and SharePoint scripts are loaded
// Place any code you want to run when Default.aspx is loaded in this function
// The code creates a context object which is needed to use the SharePoint object model
function sharePointReady() {
    // retrieve passed app web/host web URLs..
    hostweburl = decodeURIComponent($.getUrlVar("SPHostUrl"));
    appweburl = decodeURIComponent($.getUrlVar("SPAppWebUrl"));
    loadDependentScripts();
}
 
function loadDependentScripts() {
    var scriptbase = hostweburl + "/_layouts/15/";
 
    // Load the js files and continue to the successHandler
    $.getScript(scriptbase + "SP.Runtime.js",
        function () {
            $.getScript(scriptbase + "SP.js",
                function () { $.getScript(scriptbase + "SP.RequestExecutor.js", runCode); }
                );
        }
    );
}
 
function runCode() {
    loadUser();
}
 
function loadUser() {
    var ctx = new SP.ClientContext.get_current();
    user = ctx.get_web().get_currentUser();
 
    ctx.load(user);
    ctx.executeQueryAsync(onGetUserNameSuccess, onGetUserNameFail);
}
 
function onGetUserNameSuccess() {
    fetchedUserName = user.get_title();
 
    // now we have a username, we can execute our main query..
    fetchTargetHoursFromHostWebUsingREST();
}
 
// This function is executed if the above OM call fails
function onGetUserNameFail(sender, args) {
    alert('Failed to get user name. Error:' + args.get_message());
}
 
function fetchTargetHoursFromHostWebUsingREST() {
    var executor;
 
    // although we're fetching data from the host web, SP.RequestExecutor gets initialized with the app web URL..
    executor = new SP.RequestExecutor(appweburl);
    executor.executeAsync(
        {
            url:
                appweburl +
                "/_api/SP.AppContextSite(@target)/web/lists/getbytitle('" + utilTargetsList + "')/items?@target='" +
                hostweburl + "'&$filter=Title eq '" + userName + "'&$select=Target",
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: onGetTargetHoursByRESTSuccess,
            error: onGetTargetHoursByRESTFail
        }
    );
}
 
function onGetTargetHoursByRESTSuccess(data) {
    var jsonObject = JSON.parse(data.body);
    $('#targetHours').text(jsonObject.d.results[0].Target);
}
 
function onGetTargetHoursByRESTFail(data, errorCode, errorMessage) {
    alert('Failed to get host site. Error:' + errorMessage);
}

If you prefer to use the JavaScript CSOM instead of REST, you can do that – let’s look at a sample.

4. Querying for specific items in a specific list in the host web (JavaScript CSOM)

[NOTE – unlike the other techniques, I suspect this one won’t work in other app scenarios such as a provider-hosted app calling back to the app web/host web. To repeat, I’m really focusing about a page in a SharePoint-hosted app here.] So the previous samples have all used REST, but let’s say that we want to use the JavaScript client object model instead. In this case, use of the cross-domain library is not necessary – but there is a trick to being able to query the host web rather than the app web. Remember the app web is the default context because that’s where this code is running. To get to the host web using CSOM, I need to create an instance of SP.AppContextSite, passing it details of both app and host webs. As you’ll know if you’ve used the CSOM in ‘classic’ scenarios, the data isn’t returned in JSON here, but in collections that I can enumerate:

// omitted for brevity: variable declarations, setup code to load dependent scripts, get hostweburl from querystring etc. (see previous example for this) 
 
// start of core methods to fetch host web data using JavaScript CSOM..
function fetchTargetHoursFromHostWebCSOM() {
    appWebContext = new SP.ClientContext.get_current();
    var hostWebContext = new SP.AppContextSite(appWebContext, hostweburl);
 
    var targetHoursList = hostWebContext.get_web().get_lists().getByTitle(utilTargetsListFriendlyName);
    var query = new SP.CamlQuery();
    query.set_viewXml("<View><Query><Where><Contains><FieldRef Name='Title'/><Value Type='Text'>" + userName + "</Value></Contains></Where></Query></View>");
 
    targetHoursResult = targetHoursList.getItems(query);
    appWebContext.load(targetHoursResult);
    appWebContext.executeQueryAsync(onGetTargetHoursByCSOMSuccess, onGetTargetHoursByCSOMFail);
}
 
function onGetTargetHoursByCSOMSuccess(data) {
    var listEnumerator = targetHoursResult.getEnumerator();
    while (listEnumerator.moveNext()) {
        $('#targetHours').text(listEnumerator.get_current().get_item("Target"));
    }
}
 
function onGetTargetHoursByCSOMFail(data, errorCode, errorMessage) {
    alert('Failed to get host site by CSOM. Error:' + errorMessage);
}

NOTE:- If you need sample JavaScript for other tasks (e.g. adding an item to a list, deleting a list etc.) - check out How to: Complete basic operations using JavaScript library code in SharePoint 2013 – this deals with many such scenarios.

Tips for working with the REST _api

Determining the REST URL to use

If you’re choosing to use REST rather than CSOM, then the best tip I can give you is to work out the REST URL using the browser first, and then integrate this URL with your code (e.g. dealing with the app web/host web thing) as a second step. What I mean by this is simply typing in the browser address bar and checking the data returned - it will come back as XML by default, but you’ll still see which objects/properties come back.

So here’s me checking that I can fetch a single property of the web with a certain URL:

BrowserRequest_WebTitle
..and here’s me checking that I’ll get back all the lists in the web with a different URL:

BrowserRequest_ListsInWeb
You’ll have a far quicker feedback cycle with this approach, rather than waiting for the app to deploy for each change.

Returning XML rather than JSON

If for some reason you’d prefer to work with XML rather than JSON, then you can specify XML to be returned. Use the Accept header to do this – simply swap this line:

headers: { "Accept": "application/json; odata=verbose" }

..for:

headers: { "Accept": "application/xml" }

You’ll then need to adjust code in your success handler accordingly.

Permissions required to access host web data

Security, authentication, authorization and permission levels are all fairly big topics for SharePoint 2013 apps. However, the main point which is relevant to this article is that by default, code which accesses data in the host web (like my samples above) will not work until the app is granted permissions to data in the host web. Consider that if every app could access every user’s data, even just as read-only – well, the whole thing would be chaos.

The following bullets attempt to summarize the landscape here:

  • Like a user, an app has it’s own identity which permissions can be granted to – this is known as the “app principal”
  • If the app has an app web, the app principal automatically has Full Control to this area – nothing extra is required to read/write data in here
  • In contrast, the app only has “basic permissions” to the host web or any other SharePoint site – you can get some core properties such as the web title, but not much more
  • The app developer must code the app with a “permission request” specifying which data in the host web the app should be able to work with
  • This is accomplished in the AppManifest.xml file – Visual Studio even has a designer to help get the XML right
  • The person installing the app will then see which permissions are required, and must agree to this for the app to be installed

On a related note, in case you’re wondering apps are siloed from each other and cannot read/write each other’s data.

I mentioned that Visual Studio has a designer which helps with this – here it is with the app permissions section highlighted:

AppManifestDesigner_PermissionRequests

Although you may mainly work with AppManifest.xml in designer mode, for clarity let’s now consider things in “Code View” mode. In the examples above and in the time-tracking app I’m building, I want to read data throughout the host web. In this case, I need a permission request like this:

<AppPrincipal>
  <Internal />
</AppPrincipal>
 
<AppPermissionRequests>
  <AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="Read" />
</AppPermissionRequests>

For completeness, this is my full AppManifest.xml which contains this:

<?xml version="1.0" encoding="utf-8" ?>
<App xmlns="http://schemas.microsoft.com/sharepoint/2012/app/manifest"
     Name="COBSharePointAppsTimeTracking"
     ProductID="{5fec590d-3fd3-410d-82d9-77d13c2d3bdf}"
     Version="1.0.0.0"
     SharePointMinVersion="15.0.0.0"
>
  <Properties>
    <Title>COB Time Tracking app</Title>
    <StartPage>~appWebUrl/Pages/Default.aspx?{StandardTokens}</StartPage>
  </Properties>
 
  <!-- start of permission-related elements -->  
  <AppPrincipal>
    <Internal />
  </AppPrincipal>
 
  <AppPermissionRequests>
    <AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web" Right="Read" />
  </AppPermissionRequests>
  <!-- end of permission-related elements -->  
</App>

In many respects, demanding to read all data in the web is “a big ask” for an app. It might be more appropriate to restrict this to a specific list, in which case the declaration would look like:

<AppPrincipal>
  <Internal />
</AppPrincipal>
 
<AppPermissionRequests>
  <AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web/list" Right="Read" />
</AppPermissionRequests>

Notice that I can’t specify a particular list. I’m guessing Microsoft designed things this way because lists are pretty dynamic (and have unique identifiers assigned on creation) – so, what happens here is that the administrator installing the app selects *which* list the app has permissions to from a dropdown of all lists in the site:

AppInstall_ListPermissionRequest

Permission requests go far beyond data in SharePoint sites. As an app developer, you can also specify that the app needs to be allowed to use core SharePoint services such as search, taxonomy, user profiles and so on. Similarly it’s possible to specify “Capability Prerequisite” and “Feature Prerequisites”, to ensure the app is only used in environments which actually have these services running.

So, as you might imagine there are many permutations of permission requests – for more details, see App permissions in SharePoint 2013.

Summary

Some apps will need to access “real” end-user data outside of the app’s storage area (app web), though the must request permissions (and be granted them at install time) to do this. Several techniques can be used, including REST and the JavaScript CSOM (also known as JSOM). This article presents some code samples, as well trying to be clear on which of the many remote data scenarios they apply to.

In the future I’ll revisit the topic of using the JavaScript cross-domain library in remote provider-hosted apps.

Next time: Working with web parts within a SharePoint app

13 comments:

Hrayr Diloyan said...

Hey Chris,
Thanks for another great post.

As far as I understood alternative approach of cross domain library to get cross domain host web data can be using SharePoint proxy(SP.WebProxy and .SP.WebRequestInfo), specifyning RemoteEndpointUrl in app.manifest.
Do you have any info about pros and cons between this two approaches(except the fact that in SharePoint proxy case we'll need to specify host web url in app manifest).

Thanks,
Hrayr

Chris O'Brien said...

Hey Hrayr,

I haven't yet used SP.WebProxy/SP.RequestInfo, but my understanding was that these were for calling remote services which don't have anything to do with SharePoint. So for example, if I wanted to call some arbitrary service on the internet from JavaScript within an app, I'd need to use these objects.

Please let me know if you find any different though!

Cheers,

Chris.

Hrayr Diloyan said...

Thanks Chris.
Yeah, agree, web proxy is mostly used for outbound calls and cross domain or Oauth for inbound calls, however i was still able to retrieve some data using web proxy for inbound scenarios too and was wondering about significant differences between them two.
will keep on playing with this, to see if i can find anything :)

Basant Pandey said...

Thanks for your research to help us for understanding this topic more clearly...

Anonymous said...

Thank you very much for these helpful blog Posts!

I want to write an app that implements the following functionality for a public facing Website that has anonymous Access enabled:

1. Let the user enter some data (like in a Survey)
2. Store that data into a host/app web list

Can that list be secured so that no entries can be retrieved with anonymous Access?
AFAIK one could simply use client side JSOM code to retrieve those list entries?

It would be very helpful to know if the above is possible, and if yes how to achieve that functionality.

Irfan Malik said...

Hi Chris,
I guess u may have figured this by now that while debugging a Cloud-Hosted app it will open in localhost always.
You need to Deploy from visual studio to run on Azure instead.

-Irfan Malik

Chris O'Brien said...

@Irfan,

Indeed. I spoke to the VS/SharePoint tools guys recently, they are working on enabling more debugging scenarios in the cloud. But for now, running locally (on http://localhost) is indeed what's needed for debugging a provider-hosted app.

Cheers,

Chris.

Doug said...

Hey Chris,
I am having extreme difficulty doing the simplest of things. I have a simple aspx page in a sharepoint hosted app, and on that page I have an html button, with a javascript method behind the button. The javascript executes when I click the button, and in the button code I wish to access a sharepoint (2013) list.

I am getting stuck because I cannot seem to get a "hook" into the SP js script. Here's my code:

var hostWebUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));

var scriptbase = hostWebUrl + "/_layouts/15/";

$.getScript(scriptbase + "SP.Runtime.js",
function () {
$.getScript(scriptbase + "SP.js",
function () { $.getScript(scriptbase + "SP.RequestExecutor.js", runcode); }
);
}
);

var context = new SP.ClientContext.get_current();


The aspx page is added to the sharepoint page as a webpart inside of an iFRAME.

At runtime after clicking the button, I get an error at "SP.ClientContext.get_current()" telling me it cannot find SP.

What's missing here?

Thanks.
Doug

Doug said...

Chris, I answered my own question. I did not have a reference to MicrosoftAjax.js in my aspx page. As soon as I figured out I needed to add that, then it works.

< s.c.r.i.p.t. type="text/javascript" src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js">

Thanks for a great site.

Chris O'Brien said...

@Doug,

Ah very good, glad you got sorted - I was still thinking about that one :)

I've also noticed other symptoms/error messages that essentially point to not having a reference to MicrosoftAjax.js. I've got a 'trouble-shooting apps' post coming up to talk about some observations like this.

Cheers,

Chris.

Doug said...

Chris, A more difficult question this time: In a SharePoint hosted app, how do I access a *Page* (i.e. DOM) element that exists on the host *Page* (i.e., a DOM element on the page that is hosting the app part, and not a SharePoint artifact in the host *Web*)? I am able to do this when I write a SP hosted app that runs from a ribbon button click, but when I am writing a SP hosted app that loads with the page in a web/app part, the host *Page* elements are not visible to me. I can't figure out how to access these items. If I let the page load completely, and then press F12 to look at the developer view, I can find the DOM items readily, but can't seem to reference them in the SharePoint hosted app during the document.ready process.

Thoughts?
Thanks.

Zheng Zhang said...

Hi Chris, thanks for sharing this.

But in my scenario, i'm not using a SP App, if i just use a mvc website, and through JSOM i got access denied error.

So is it possible to access SP data via JSOM without SP APP context please?

Thanks,
ZZ

Adam Toth said...

Hey Doug,

If you want to access the host page and its DOM information, you'll need to use the postMessage api to communicate cross-domain. Since the AppPart is an iframe hosted on a different domain from the host page, it is subject to the browser's cross-domain security restrictions, and cannot reach up into the host page or access it's elements.

That's a good thing, since you wouldn't want some third party app part scraping sensitive data from the page it sits on without your knowledge or explicit consent.

I wrote about an approach to opt-in to sharing some host page information using the postMessage api:

http://www.lifeonplanetgroove.com/use-postmessage-shim-give-sharepoint-client-app-parts-information