Sunday, 25 November 2012

Beware! SharePoint 2013 RTM apps bug/gotcha with SPHostUrl parameter

There is an interesting behavior in SharePoint-hosted apps (or apps with a SharePoint-hosted component), which many app developers will run into at some point. I mentioned it in an earlier article - at the time, I said that it was a bug in the Preview version of SP2013 which would presumably be fixed in the RTM release. But it hasn’t! So, developers need to be aware of this issue and depending what your app does, potentially write special code to work around it.

Sidenote: a future Visual Studio update may deal with this problem for you, by injecting code similar to my workaround below (if this happens, I will update this article here). For now, however, developers must deal with the problem themselves – the relevant Microsoft folks agreed more awareness of the issue would be a good thing in the interim, so this article is my attempt to help!

These are the circumstances where you’ll need to care:

  • Your app is SharePoint-hosted (or at least has some part of it which is SharePoint-hosted, and therefore uses an app web – maybe it’s predominantly a cloud app [auto-hosted/provider-hosted] but uses some lists/pages in the app web)
  • Your app accesses data in the host web, and uses the SPHostURL querystring parameter for this

The issue

To ‘enter’ an app, an end-user clicks on the app’s icon in the Site Contents page – I think of this as the front door to the app. When they do this they hit a system page called appredirect.aspx, which (unsurprisingly) redirects them to the app’s home page – as it does so, it passes some information as querystring parameters in the URL to the app, including:

  • SPHostUrl – the full URL of the host site (i.e. where we have just been redirected from)
  • SPAppWebUrl – the full URL of the app web (i.e. where we are being redirected to)
  • SPLanguage – the supported locale of the app
  • SPClientTag/SPProductNumber – other tokens used to identify the app

Often, the app’s code will make use of these. For example, in my time-tracking application I use SPHostUrl in some JavaScript CSOM code, because there’s a list in the host web which I read some information from. All these parameters are correctly populated when the user is redirected from the host web into the app. However, once in the app, if the user navigates around some pages there and uses the in-built breadcrumb link to return to the app’s home page – the parameters are put back in place by the app framework, but the SPHostUrl parameter now contains the wrong value. Specifically, it now has the value for the app web URL (also in the SPAppWebUrl parameter), which can cause all sorts of fun for your code!

Before we discuss workarounds, let’s just consider how this might manifest itself in your code. 

Possible symptoms of the bug

Symptom 1 – asking for the lists in the web gives the ‘wrong’ set:

Let’s say you had some code to fetch the lists in your host web (i.e. end-user data). Using the Developer Site template, I see this if I print them out to the screen (this code is taken from example 3 in my previous article):

Lists_1stPageLoad 
..but when I move around pages/lists in my app, when I come back to the page with this code I now get a completely different set of lists!

Lists_2ndPageLoad

Symptom 2 – referencing a list by name results in an error:

Similarly, let’s consider some ‘real’ code in my learner app – this asks for a list called ‘Utilisation targets’ with the following line of CSOM code:

var targetHoursList = hostWebContext.get_web().get_lists().getByTitle('Utilisation targets');

Once I’ve got the list, I query for an item associated to the current user and ultimately display the value of the ‘Target’ field on the page:

FetchListData_1stPageLoad
However, when I navigate around my app and come back to the app’s home page, I find the same code gives an error and I’m suddenly in the ‘failure handler’ method for this code – this uses a simple JavaScript alert to display the error to the user:

FetchListData_2ndPageLoad

..and for clarity, here’s that code:

function onGetTargetHoursByRESTFail(data, errorCode, errorMessage) {
alert('Failed to get host site. Error:' + errorMessage);
}

As you can imagine, this behavior can be quite confusing to the developer! And, of course, there’s a whole range of potential symptoms in addition to the two I’ve listed here – it really depends what your code is doing. What’s that saying? If it looks like a bug and feels like a bug…!

The workaround

Since the SPHostUrl value is correct when the user initially enters the app, the workaround is to save the value at this point and refer to the saved string from then on. You could either save it client-side in a cookie, or perhaps use some server-side persistence with an appropriate key. My learner app takes the JavaScript/cookie approach. First we need some helper methods to get/set the cookie -  there are many samples on the internet, so either find a nice jQuery plugin or use whatever else does the job. I’m using the code below to set a session cookie, which includes a jQuery plugin to simplify actually getting the querystring parameters from the URL  (apologies for lack of credit, but thanks/kudos to whoever the authors are):

EDIT JAN 2013 – updated this code so that cookie has a PATH and did some tidying:

Then in my sharePointReady() method, I detect if this is either the first entrance (and grab the host URL from the querystring/store in cookie) or a subsequent page load (and use the value stored in the cookie) accordingly:

Hopefully this illustrates possible workarounds. In production, you may want to think about the server-side option or least encrypting the value if using a client-side cookie – that way URLs to the SharePoint sites which use the app aren’t hanging around in plain text on lots of client machines. Of course, vendors and individuals who supply apps to the ‘public’ via the Store should particularly bear this in mind.

Download this code

I've put the full JavaScript into a .txt file you can download - JavaScript code to workaround SPHostUrl issue

Summary

Personally I find it weird that this one got through to the released version of SP2013, but there you go. I guess it’s not too catastrophic to work around it once the issue has been identified. In summary, an app which uses the SPHostUrl parameter is likely to run into problems, but some custom code to persist the initial value can be used to work around this.

As I say, if Microsoft do provide a fix I’ll keep you posted and will also update this article.

6 comments:

bhrapp said...

Chris, thanks a lot for a well written and comprehensive article!
I'm working on a Bing Maps app demo myself created as SharePoint-hosted, and one of my ideas is to to use data stored in lists on the host web as parameters for calling web services to eg. find locations and calculate routes to destinations. You can see the startup here: http://spviking.com/2012/11/18/a-sharepoint-2013-bing-map-app-part-1/ .
My question is: is the SPHostUrl parameter the preferred method for reading data from a list on the host web into your app or are there alternative ways. Best regards Bjoern H Rapp

Salvatore Di Fazio said...

Tnx man

Anonymous said...

When are you going to learn that Microsoft releases software such as SharePoint 2013 based on revenue needs, not based on whether the software is really ready for release?

This has been going on for decades and frankly I'm disappointed that you, an MVP, of all people, haven't figured that out yet.

The fact that you have to write work-around code for something as simple as tracking url and query string information is not only disappointing, it's dangerous: people will be copying/pasting your work-around for years to come because they either won't know when the problem is fully fixed or will not see the copious amounts of comments that describe when/how to remove the work-around code one Microsoft fixes the issue.

Your "work-around" will become a dangerous, permanent "fixture" in all sort of production code.

Chris O'Brien said...

@Anonymous,

Hmm, well I guess we have different perspectives on this.

All I can really do is highlight the issue, provide an example of a workaround and discuss considerations when dealing with it. Maybe I'm wrong, but I think that's a positive contribution. Additionally, from the conversations I've had with Microsoft, it sounds like it will be pretty clear to the developer that Microsoft are providing the workaround since some extra JavaScript code will be added to app.js, alongside the existing starter code (that the developer has to delete/modify if they are building a real app). And for what it's worth, it sounds like their code might not be too disimilar to my code.

In terms of whether I need to learn about how Microsoft release software - well, I've learnt enough for it not to keep me awake at night. I'm not defending Microsoft. I'm not even saying they got it right in this case. But, I don't feel bad for helping smooth out some of these gaps and provide info that might be useful to those going through the same things I am. To me, that's a positive community contribution.

Thanks,

Chris.

Alex Dean said...

Chris,
Thank you for saving our bacon once again! Great article, simple to follow fix and good coding practices all round.
I can not see anything inherently dangerous in your approach. Having to track users is nothing new and I think too many developers have gotten lazy and expect whatever framework they use to do far too many jobs for them.

Also, respect for publishing such a negative and pointless comment by a person who would not even stand up to his name.

Chris O'Brien said...

@Alex,

Thanks - appreciate the vote of confidence.

Cheers,

Chris.