Tuesday, July 16, 2013

SharePoint 2013 Apps: “Invalid JSON Data” JavaScript exception when loading the Chrome Control declaratively

SharePoint 2013 introduces the new app model making it possible to integrate externally hosted apps into SharePoint. One of the features of the new app framework is the so called “Chrome Control” which enables the externally hosted apps to dynamically load the SharePoint CSS and thus implement the same look and feel as SharePoint. There is a nice example on how to use the Chrome Control at http://msdn.microsoft.com/en-us/library/fp179916.aspx, so I will not go into the basics here. Rather, I want to point you to a nasty bug in the current SharePoint 2013 version which might affect you if you are loading the Chrome Control declaratively.

Note: you only need the Chrome Control in your provider or autohosted apps that run outside of the SharePoint context. You don’t need the Chrome Control in the SharePoint hosted apps since those run in the SharePoint context and can reference the necessary styles by referencing the SharePoint master page.

A little bit of background first. There are two ways to load the Chrome Control into your app: the JavaScript way and the declarative way.

The JavaScript way to load the Chrome Control looks something like this: first you need a page in your app with a placeholder that will host the Chrome Control once it’s loaded, e.g. (removed details for simplicity):

...
<body style="display: none">
    <form id="form1" runat="server">
        <!-- Chrome control placeholder -->
        <div id="chrome_ctrl_container"></div>
 
        <!-- The chrome control also makes the SharePoint
          Website stylesheet available to your page -->
        <h1 class="ms-accentText">Main content</h1>
        <div id="MainContent">
 
        </div>
    </form>
</body>
...

In this case I have a <div id=”chrome_ctrl_container”> tag where the Chrome Control will be loaded. Once you have this, you can easily load the Chrome Control using JavaScript like this:



   1: var SPWorkshop = window.SPWorkshop || {};
   2:  
   3: SPWorkshop.ChromeControl = function () {
   4:  
   5:     var render = function () {
   6:         "use strict";
   7:         // We are using document.URL.split("?")[1] to pass the original query string parameters
   8:         // to all other pages. Note that this is currently possible only when using JavaScript
   9:         // to render Chrome control, the declarative rendering has a bug which prevents the 
  10:         // document.URL.split("?")[1] parts to be interpreted correctly.
  11:         var options = {
  12:             "appIconUrl": "../Images/AppIcon.png",
  13:             "appTitle": "SharePoint 2013 App",
  14:             "appHelpPageUrl": "../Pages/Help.aspx?" + document.URL.split("?")[1],
  15:             // The onCssLoaded event allows you to 
  16:             //  specify a callback to execute when the
  17:             //  chrome resources have been loaded.
  18:             "onCssLoaded": "SPWorkshop.ChromeControl.chromeLoaded()",
  19:             "settingsLinks": [
  20:                 {
  21:                     "linkUrl": "../Pages/Contacts.aspx?" + document.URL.split("?")[1],
  22:                     "displayName": "Contacts"
  23:                 },
  24:                 {
  25:                     "linkUrl": "../Pages/Welcome.aspx?" + document.URL.split("?")[1],
  26:                     "displayName": "Welcome"
  27:                 }
  28:             ]
  29:         };
  30:  
  31:         var nav = new SP.UI.Controls.Navigation("chrome_ctrl_container", options);
  32:         nav.setVisible(true);
  33:     };
  34:  
  35:     // Callback for the onCssLoaded event defined
  36:     //  in the options object of the chrome control
  37:     function chromeLoaded() {
  38:         "use strict";
  39:         // When the page has loaded the required
  40:         //  resources for the chrome control,
  41:         //  display the page body.
  42:         $("body").show();
  43:     }
  44:  
  45:     return {
  46:         render: render,
  47:         chromeLoaded: chromeLoaded
  48:     };
  49: }();
  50:  
  51: SPWorkshop.Helper = function () {
  52:     var getQueryStringParameter = function (p) {
  53:         "use strict";
  54:         var params = document.URL.split("?")[1].split("&");
  55:         var strParams = "";
  56:         for (var i = 0; i < params.length; i = i + 1) {
  57:             var singleParam = params[i].split("=");
  58:             if (singleParam[0] === p)
  59:                 return singleParam[1];
  60:         }
  61:     };
  62:  
  63:     return {
  64:         getQueryStringParameter: getQueryStringParameter
  65:  
  66:     };
  67: }();
  68:  
  69: $(document).ready(function () {
  70:     "use strict";
  71:     //Get the URI decoded URL.
  72:     var hostWebUrl = decodeURIComponent(SPWorkshop.Helper.getQueryStringParameter("SPHostUrl"));
  73:  
  74:     // The SharePoint js files URL are in the form:
  75:     // web_url/_layouts/15/resource
  76:     var scriptBase = hostWebUrl + "/_layouts/15/";
  77:  
  78:     // Load the js file and continue to the success handler
  79:     $.getScript(scriptBase + "SP.UI.Controls.js", SPWorkshop.ChromeControl.render);
  80: });

So pretty much just copy-paste from the MSDN article above. The important part is the SPWorkshop.ChromeControl.render() method. Here we first define the options for the Chrome Control (lines 11-29) and then load it in the line 31. In the options I specified several other pages that should be linked up in the Chrome Control (Contacts.aspx, Welcome.aspx, Help.aspx) and I want to pass the original URL query string to all those pages so that they can access all the SharePoint context info passed in the URL. I did it like this:


"linkUrl": "../Pages/Contacts.aspx?" + document.URL.split("?")[1]


And this works fine, when I load the page, my Chrome Control is loaded and when I click on the “Settings” wheel in the top right corner I can see the link to the Contacts.aspx page in the status bar containing the query string with the SharePoint context info:


image


When I click on the Contacts link, I am redirected to the Contacts.aspx page and the entire query string is copied over, so my URL looks something like


http://localhost:59891/Pages/Contacts.aspx?SPHostUrl=http%3A%2F%2Fdev%2Econtoso%2Edev&SPLanguage=en%2DUS&SPClientTag=2&SPProductNumber=15%2E0%2E4505%2E1005&SPHostTitle=Contoso%20Developer%20Site


See that pulp after Contacts.aspx? That’s the context info that SharePoint passed to my Default.aspx and that I now passed on to the Contacts.aspx thanks to that “…document.URL.split("?")[1]” line in the Chrome Control options. So far, so good.


Now on to the problem. The alternative way to load the Chrome Control in your page is declaratively through your aspx markup, something like this:



...
<body>
    <form id="form1" runat="server">
        <div id="chrome_ctrl_container" 
            data-ms-control="SP.UI.Controls.Navigation"
            data-ms-options=
            '{ 
                "appIconUrl": "../Images/AppIcon.png", 
                "appTitle": "SharePoint 2013 App", 
                "appHelpPageUrl": "../Pages/Help.aspx?" + document.URL.split("?")[1], 
                "settingsLinks": [ 
                    { 
                        "linkUrl": "../Pages/Contacts.aspx" + document.URL.split("?")[1], 
                        "displayName": "Contacts" 
                    }, 
                    { 
                        "linkUrl": "../Pages/Welcome.aspx" + document.URL.split("?")[1], 
                        "displayName": "Welcome" 
                    } 
                ] 
            }'>
        </div>
        <div>
            <h1 class="ms-accentText">Hello World</h1>
        </div>
    </form>
</body>
...

See those “data-ms-control” and the “data-ms-options” attributes? The JavaScript code in the SP.UI.Controls.js recognizes them and renders the Chrome Control in the parent HTML element (here the “chrome_ctrl_container” div).


Now all you need to actually render the control at runtime is this piece of JavaScript in your document load handler:



$(document).ready(function () {
    "use strict";
    //Get the URI decoded URL.
    var hostWebUrl = decodeURIComponent(SPWorkshop.Helper.getQueryStringParameter("SPHostUrl"));
 
    // The SharePoint js files URL are in the form:
    // web_url/_layouts/15/resource
    var scriptBase = hostWebUrl + "/_layouts/15/";
 
    // Load the js file and continue to the success handler
    $.getScript(scriptBase + "SP.UI.Controls.debug.js");
    
});

However, when you deploy your app and browse to the page, instead of the Chrome Control you will be greeted by the nasty “Invalid JSON data".” JavaScript exception and a blank screen:


image



After some digging in the SP.UI.Controls.js I found the root cause. When declaring the Chrome Control declaratively in the aspx markup, the SP.UI.Controls.js checks if the Chrome options passed in the data-ms-options attribute are a valid JSON string according to the JSON standard from http://json.org, and in this case this is NOT a valid JSON thanks to those “ + document.URL.split("?")[1]” calls that are supposed to pass the original query string to other pages. However, in this case this string passed in the data-ms-options attribute doesn’t have to be a valid JSON string, since it is later executed using the JavaScript eval() function, so it should only be a valid JavaScript code which it is. So we have a bug here.


To prove that this is correct, remove the “+ document.URL.split("?")[1]” from the markup and redeploy your app again, this time the Chrome should load fine, except that your links to the Contacts.aspx and other pages now no longer get all the nice info from the URL query string (again, pay attention to the URL in the status bar, now without the query string):



<body>
    <form id="form1" runat="server">
        <div id="chrome_ctrl_container" 
            data-ms-control="SP.UI.Controls.Navigation"
            data-ms-options=
            '{ 
                "appIconUrl": "../Images/AppIcon.png", 
                "appTitle": "SharePoint 2013 App", 
                "appHelpPageUrl": "../Pages/Help.aspx?", 
                "settingsLinks": [ 
                    { 
                        "linkUrl": "../Pages/Contacts.aspx", 
                        "displayName": "Contacts" 
                    }, 
                    { 
                        "linkUrl": "../Pages/Welcome.aspx", 
                        "displayName": "Welcome" 
                    } 
                ] 
            }'>
        </div>
        <div>
            <h1 class="ms-accentText">Hello World</h1>
        </div>
    </form>
</body>

image


So how do you pass the query string to the pages called from the Chrome Control when it is declared declaratively in the aspx markup? You don’t, there is no way to circumvent this bug unless you change the SP.UI.Control.js which is not supported. In other words, you will have to use the JavaScript way to load the Chrome Control until this is fixed.


Note: examples similar to this one with attempts to pass the query string to the pages in a declarative Chrome Control were used in highly popular and otherwise highly recommended books “Inside Microsoft SharePoint 2013” and “Microsoft SharePoint 2013 App Development” and they won’t work either due to the same problem – so don’t waste your time looking for the solution, there is none at this moment (SP 2013 with April 2013 CU).

Friday, May 3, 2013

SharePoint 2010: Purging the Activity Feed

SharePoint 2010 per default aggregates the last 14 days worth of social activities and displays them on the user’s MySite.

Recently I noticed that when user A leaves a note on user B’s wall, and subsequently deletes this note, that the note will only be deleted from the user A’s wall but will still show on the user B’s wall.

The only way to delete individual activities is by directly accessing the User Profile Database which is not supported, However, it is easy to delete all activities:

$ups = Get-SPServiceApplication <GUID of UPA> 
$ups.DaysWorthOfActivityFeedsToKeep = 0
$ups.Update()
Now execute the User Profile Service Application - Activity Feed Cleanup Job Timer job – this will purge all activites older than 0 days, i.e. all activities from the queue.
After you’re done, you can return to the default setting of 14 days:
$ups = Get-SPServiceApplication <GUID of UPSA>
$ups.DaysWorthOfActivityFeedsToKeep = 14
$ups.Update()

Adding custom WCF services to a SharePoint 2013 farm solution using Visual Studio 2012

In this blog post I will explain how to add and deploy a custom WCF service as a part of a SharePoint 2013 farm solution using Visual Studio 2012.

Back in the SharePoint 2010 days we all learned to love and use the Community Kit for SharePoint Development Tools (CKSDev), the king of Visual Studio add ons for SharePoint developers which extended the Visual Studio 2010 with numerous SharePoint related options and project item templates, making SharePoint development so much easier.

One of these templates was the Custom WCF Service template which made it easy to add a custom WCF service to your SharePoint 2010 solution. At the time of this writing, the CKSDev Tools were upgraded to the version 1.1 which made them compatible with Visual Studio 2012. However, many of the features from the 2010 version - most notably the SharePoint related project item templates such as Custom WCF Service - are missing from this release. The missing features will most certailnly be re-added in the coming versions, but if you’re in a hurry, this article will show you how to add a custom WCF service to your SharePoint 2013 project in Visual Studio 2012 per hand, without any add ons at all.

Before you begin, make sure to update your version od the Microsoft Office Developer Tools for Visual Studio 2012 the Visual Studio 2012 itself to the latest versions. This is especially important if you are still using the pre-RTM version of the Office Developer Tools, since this version has a many problems when developing against the RTM version of SharePoint 2013.

Begin by creating a new SharePoint 2013 empty project. In the Visual Studio 2012 menu select File>New>Project and select the “SharePoint 2013 – Empty Project” template from the SharePoint Solutions template group:

01_CreateNewProject

I named the project VS2012WcfService but you can name it what you want.

The SharePoint Customization Wizard will display asking you to select the deployment options for your new solution. Select a valid SharePoint URL to be used for deployment and F5 debugging from Visual Studio 2012 and select the “Deploy as a farm solution” option:

02_ProjectOptions

Please note that the URL you select here must point to the local SharePoint installation. If you are using Host Names Site Collections in your dev environment, it is likely that Visual Studio 2012 will not recognize the site URL as a local URL and throw the following error:

image

If that happens, simply open your hosts file under C:\Windows\System32\drivers\etc, add the following line to it and save it:

127.0.0.1   team.contoso.com

Replace team.contoso.com with the URL of the site you want to use for the debugging. After that the above error should disappear and you should be able to use your Host Named site for debugging.

First add the necessary references to yourt project. You need to add the reference to the following two dlls:

  • Microsoft.SharePoint.Client.ServerRuntime 15.0.0.0
  • System.ServiceModel 4.0.0.0

image

The next step is to add the ISAPI SharePoint mapped folder to the project. The ISAPI folder is the special folder within the SharePoint hive where all WCF services should be deployed. This folder is mapped to the “_vti_bin” alias in the IIS, so that services deployed to this folder can be accessed through http://{SiteUrl}/_vti_bin/{ServiceName}.svc.

Right-click on the project node in the solution explorer and select Add>SharePoint Mapped Folder… menu option:

03_AddMappedFolderMenu

On the next dialog select the ISAPI folder and click on OK:

04_ISAPIFolder

Once you did that, right-click on the ISAPI folder in the Solution Explorer and add a subfolder to store files for your custom WCF service. This is technically not necessary, but as a best practice I suggest to always separate all your custom files being deployed in the SharePoint hive in subfolders named after your solution and/or company. In this example I added the “MyService” subfolder:

05_AddMyServiceFolder

Keep in mind that the name of the subfolder will also impact the URL of your custom service after it is deployed to SharePoint. In this case, my service(s) deployed in the ISAPI\MyService folder will be accessible under http://{SIteUrl}/_vti_bin/MyService/{ServiceName}.svc URL.

Next, we’ll add the custom service svc file to the ISAPI\MyService folder. Since we don’t have the project item template to add a WCF service directly, we’ll add a simple text file and change its extension to svc. Right-click on the MyService subfolder, select Add>New Item… menu:

06_AddNewItemMenu

On the next dialog, select the Text File template under the General template group and name the file MyService.svc (or anything you want but make sure it has the .svc extension):

07_AddMyServiceSvc

Next, add the Interface for our new service. Right-click again on the MyService folder in the solution exporer, select Add>New Item and select the Interface template under the Code template group:

08_AddIMyService

Name the new interface I{ServiceName}.cs, in my case it is IMyService.cs. Later we will add code to this interface that will describe the Operation Contract of our new service, but let’s first add the last file we need, the {ServiceName}.svc.cs – in my case MyService.svc.cs - which will contain the implementation of the service interface we just added.

Right-click on the MyService folder in the solution explorer, select Add>New Item… menu option and add a new class making sure to name it like {ServiceName}.svc.cs:

08_AddMyServiceSvcCs

Time for a quick check. If you did everything right, your project structure in the solution explorer should look something like this now:

08_ProjectStructure

So far we added:

  • MyService.svc – the service file itself that will be deployed to SharePoint hive and served by IIS
  • IMyService.cs – the interface file that will contain the service operation contract
  • MyService.svc.cs – the code file that will contain the service interface implementation

Let’s now fill the files with content.

First open the IMyService.cs file and define your Operation Contract. For the purpose of this example I will use the following simple contract:

using System.ServiceModel;
 
namespace VS2012WcfService
{
    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        string HelloWorld();
    }
}

As you can see, we only have one service operation called HelloWorld returning a string. Let’s implement this interface. Open the MyService.svc.cs file and paste the following code:





using Microsoft.SharePoint.Client.Services;
using System.ServiceModel.Activation;
 
namespace VS2012WcfService
{
    [BasicHttpBindingServiceMetadataExchangeEndpoint]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    public class MyService : IMyService
    {
        public string HelloWorld()
        {
            return "Hello World from WCF and SharePoint 2013";
        }
    }
}


The next step ist to connect everything together in the MyService.svc file. Open MyService.svc and paste the following XML into it:




<%@ ServiceHost Language="C#" Debug="true"
    Service="VS2012WcfService.MyService, $SharePoint.Project.AssemblyFullName$"  
    CodeBehind="MyService.svc.cs"
    Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressBasicHttpBindingServiceHostFactory, 
    Microsoft.SharePoint.Client.ServerRuntime, 
    Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

Make sure that the namespace and the class name of your service (in my case VS2012WcfService.MyService) and the CodeBehind file name (in my case MyService.svc.cs) entered above match those in your project.


Pay attention to the following line in the above XML:


Service="VS2012WcfService.MyService, $SharePoint.Project.AssemblyFullName$"


See the $SharePoint.Project.AssemblyFullName$ token? Visual Studio should replace this token the actual assembly name and version during the packaging of the solution. However, per default Visual Studio does that only for some file types. If you check the Microsoft.VisualStudio.SharePoint.targets MSBuild target file under C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v11.0\SharePointTools (which is used by Visual Studio to build SharePoint projects) you will find the following line:


<TokenReplacementFileExtensions>$(TokenReplacementFileExtensions);xml;aspx;ascx;webpart;dwp;bdcm</TokenReplacementFileExtensions>


This line basically tells Visual Studio to replace tokens such as $SharePoint.Project.AssemblyFullName$ in .XML, .ASPX, .ASCX, .WEBPART, .DWP and .BDCM files. In addition, there is an extension point $(TokenReplacementFileExtension) that makes it possible to register additional file types either by passing them to MSBuild command line or by modifying the Project (.proj) file.


So this is what we need to do in order to add the .SVC files to the list of file types whose tokens will be replaced by Visual Studio during pacaking. Save your project and then right-click on the project name in the Solution Explorer and select Unload Project option:


image


After the project was unloaded, right click again on the project name and select the “Edit VS2010EcfService.csproj” option:


image


This will open up the .csproj XML file in the editor. In the first <PropertyGroup> section on the top of the file, right below the line where it says


<SandboxedSolution>False</SandboxedSolution>


add the following line, save the file and close the editor:


<TokenReplacementFileExtensions>svc</TokenReplacementFileExtensions>


This will effectively tell Visual Studio to include .svc files to the list of files for token replacement.


The last step is to reload the project, compile and deploy it.


Right-click on the project name in the solution explorer and select the Reload Project option:


image


That’s it! Compile and deploy your project. If you did everything correctly, you should be able to access your custom WCF service under http://{SiteUrl}/_vti_bin/MyService/MyService.svc (replace the {SiteUrl} with the URL of the site you selected for the debugging when you started this tutorial):


image


Checking in the 15 hive, you should see your MyService.svc file deployed in 15\ISAPI\MyService folder:


image




If you open the MyService.svc file with the notepad, you will see that the $SharePoint.Project.AssemblyFullName$ token was replaced with the correct assembly name and version:


image


The dll itself was, of course, deployed to GAC. Because this is a .NET 4.5 dll compiled for Any CPU, it is not stored in the “old” .NET 2.0/3.5 GAC under C:\Windows\assembly, but under the new .NET 4.x GAC found at C:\Windows\Microsoft.NET\assembly\GAC_MSIL:


image