Calling SharePoint 2013 REST api using an AngularJS Service

Calling the SharePoint 2013’s REST api can be a repetitive task. During the last Office365 summit in Amsterdam, the developer track always pointed to the Office365 Patterns and Practices for samples on how to develop in office 365. To my greatest joy they also have an SPA sample!

AngularJS services provide a way to share code within the app. This is great for not always having to write the same code over and over again. In this case calling SharePoint REST urls using $http.

The following is a snippet from https://github.com/OfficeDev/PnP/blob/master/Samples/Core.SharePointProxyForSpaApps/SharePointProxyForSpaAppsWeb/App/common/services/sharepointproxy.service.js:

(function (angular) {
    "use strict";

    angular
        .module('sharepointproxy.service', [])
        .factory('SharepointProxyService', SharepointProxyService);

    function SharepointProxyService($http) {

        var proxyEndpointOptions = {
            url: 'api/sharepoint',
            method: 'POST',
            data: {}
        };

        var defaultProxyOptions = {

        };

        var service = {
            transformRequest: transformRequest,
            sendRequest: sendRequest,
            getWebTitle: getWebTitle
        };

        function getData(o) {
            return o.data;
        }

        function transformRequest(request) {
            var transformedRequest = proxyEndpointOptions;
            transformedRequest.data = request;

            return transformedRequest;
        }

        function sendRequest(request) {
            return $http(request)
                .then(getData)
                .then(function (data) {
                    return data;
                })
            ;
        }

        return service;
    }

    SharepointProxyService.$inject = ['$http'];

})(angular);

It’s a great example on how to start using an AngularJS service calling SharePoint. It can be easily extended with more defined methods, like getting the title of the hostWeb:

        function getWebTitle(hostWebUrl) {
            var requestUrl = hostWebUrl + '/_api/web/title';
            var request = {
                url: requestUrl,
                method: 'GET',
                headers: {
                    Accept: 'application/json;odata=verbose'
                }
            }
            var spRequest = transformRequest(request);
            return sendRequest(spRequest);
        }

All you need to add in your controller is a simple call to the service passing the hostWebUrl:

var queryParameterString = (window.location.search[0] === '?') ? window.location.search.slice(1) : window.location.search;
$scope.queryParameters = deparam(queryParameterString);

        $scope.requestServiceData = function () {
            console.log("Request service data!");
            var result = SharepointProxyService.getWebTitle($scope.queryParameters.SPHostUrl)
            .then(function (data) {
                $scope.serviceResponse = data;
            });
        };

Finish off by adding a button to your HTML to call the service:

<button ng-click="requestServiceData()">Request</button>

Variations.Current.UserAccessibleLabels can spawn lots of SPWebs

Be careful when using the OOTB SharePoint Publishing property Variations.Current.UserAccessibleLabels as it spawns SPWebs for every call.

To demo this I’ve created a web part and added 2 different code blocks to it with monitored scopes. The first one creates 3 objects with a direct reference to the UserAccessibleLabels property. The second one uses a local field which is only assigned once.

namespace TestVariations.TestAccessibleLabels
{
    [ToolboxItemAttribute(false)]
    public partial class TestAccessibleLabels : WebPart
    {
        private ReadOnlyCollection<VariationLabel> userAccessibleLables = null;

        public ReadOnlyCollection<VariationLabel> UserAccessibleLables
        {
            get
            {
                if (userAccessibleLables == null)
                    userAccessibleLables = Variations.Current.UserAccessibleLabels;

                return userAccessibleLables;
            }
        }

        public TestAccessibleLabels()
        {
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            InitializeControl();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                var resultBuilder = new StringBuilder();

                using (new SPMonitoredScope("Variations.Current.UserAccessibleLabels"))
                {
                    var variationLabels = Variations.Current.UserAccessibleLabels;
                    var variationLabels2 = Variations.Current.UserAccessibleLabels;
                    var variationLabels3 = Variations.Current.UserAccessibleLabels;

                    foreach (var label in variationLabels)
                    {
                        resultBuilder.Append(label.Title);
                    }

                    foreach (var label2 in variationLabels2)
                    {
                        resultBuilder.Append(label2.Title);
                    }

                    foreach (var label3 in variationLabels3)
                    {
                        resultBuilder.Append(label3.Title);
                    }
                }

                using (new SPMonitoredScope("this.UserAccessibleLabels"))
                {
                    var variationLabels4 = this.UserAccessibleLables;
                    var variationLabels5 = this.UserAccessibleLables;
                    var variationLabels6 = this.UserAccessibleLables;

                    foreach (var label4 in variationLabels4)
                    {
                        resultBuilder.Append(label4.Title);
                    }

                    foreach (var label5 in variationLabels5)
                    {
                        resultBuilder.Append(label5.Title);
                    }

                    foreach (var label6 in variationLabels6)
                    {
                        resultBuilder.Append(label6.Title);
                    }
                }

                resultLabel.Text = resultBuilder.ToString();
            }
            catch (Exception ex)
            {
                resultLabel.Text = ex.Message;
            }
        }
    }
}

If we take a look at the developer dashboard, we can see SPWebs getting generated for each variation:

spweb spawns

Using ILSpy we can see that SharePoint opens web objects to check the current user’s access.

ilspy variations

When taking a look at the loading time, we see that the impact on the total loading time is quite high when using lots of direct references.

spweb monitored scopes

For the conclusion we can say that using the OOTB SharePoint property is the way to go when you only have to use it once. However when you’re using this property all over the place in your code it’s best to keep the “result” of the UserAccessibleLabels in a private field.

Updated SharePoint Query Correlation ID Central Admin Page to 2013

Thanks to Ghislain Lerda, the SharePoint Query Correlation ID Central Admin Page has been migrated to 2013. There’s also a new functionality available on the results page which allows you to export the results to a txt file.

export to txt

export to txt result

The source of the project on CodePlex has been restructured to 2 branches: one for SharePoint 2010 and one for SharePoint 2013. You’ll find future changes for both projects on the same CodePlex site.

You can get the latest at the releases page on CodePlex:
https://spgetcorrelationpage.codeplex.com/releases

The deployment and configuration of the solution is still the same. Simply install the .wsp on your farm and check the Monitoring category page in central admin. You’ll find a link to the search page there.

PageLayouts not updating through module

Trying to deploy an updated page layout through a module to the master page gallery was going wrong horribly. The page layout inside the gallery wasn’t updating while deployment was succesful and nothing was showing up in the logs as of why there wasn’t an update done to the file. The project as example looks like this:

pagelayoutproject

As you can see it’s a simple module which will deploy “testLayout.aspx” to the master page gallery using the following elements.xml:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="PageLayouts" Url="_catalogs/masterpage" RootWebOnly="TRUE">
    <File Path="PageLayoutstestLayout.aspx" Url="PageLayouts/testLayout.aspx" Type="GhostableInLibrary">
      <Property Name="Title" Value="My Custom Page Layout" />
      <Property Name="ContentType" Value="$Resources:cmscore,contenttype_pagelayout_name;" />
      <Property Name="PublishingPreviewImage" Value="~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/CustomPageLayout.png, ~SiteCollection/_catalogs/masterpage/$Resources:core,Culture;/Preview Images/CustomPageLayout.png" />
      <Property Name="PublishingAssociatedContentType" Value=";#$Resources:cmscore,contenttype_articlepage_name;;#0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D;#"/>
    </File>
  </Module>
</Elements>

The page layout was generated through SharePoint Designer and had the following source:

<%@ Page Language="C#" Inherits="Microsoft.SharePoint.Publishing.PublishingLayoutPage,Microsoft.SharePoint.Publishing,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:webpartpageexpansion="full" meta:progid="SharePoint.WebPartPage.Document" %>

<%@ Register TagPrefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="PublishingNavigation" Namespace="Microsoft.SharePoint.Publishing.Navigation" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<asp:content contentplaceholderid="PlaceHolderPageTitle" runat="server">
	<SharePointWebControls:FieldValue id="PageTitle" FieldName="Title" runat="server"/>
</asp:content>
<asp:content contentplaceholderid="PlaceHolderMain" runat="server">
</asp:content>

Going deeper into the page’s properties it showed that the page has been customized, which was odd since nobody touched the file through SharePoint designer which would cause ghosting.

customizedpagelayout

After some research I’ve found a lot of suggestions to go the code way of manually removing the file and then add the page layout, but in my opinion it should be done through the module without code. I knew it was possible through a simple update of the module so what was going wrong? Easy: SharePoint Designer. If you look closely, the tool adds the following meta tags to the Page tag:

meta:webpartpageexpansion="full" meta:progid="SharePoint.WebPartPage.Document"

For some reason, whenever these tags are there, SharePoint will always ghost your page layouts no matter what. Removing these tags and unghosting the page was sufficient to get back those updates through modules. Phew!