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.

Find out wether a filled in user field is a group or a user

The following method allows you to find out wether a filled in user field is an actual user, or might be an active directory group. Here I use the principalInfo variable to see if there’s an actual windows principal behind the value of “user”. I use the user’s e-mail in the first part, since there might be more persons with the same user.LookupValue value. In other words: some people have the same displayname.

For the same reason, I use the user.User.LoginName in the web.EnsureUser method.

protected static SPPrincipal GetUserOrGroup(SPWeb web, SPFieldUserValue user)
{
	if (user.User != null)
		var principalInfo = SPUtility.ResolveWindowsPrincipal(
			web.Site.WebApplication, user.User.Email, SPPrincipalType.All, true);

	// if the user has no e-mail, check the user's name. This is not failsafe when 2 persons have the same name
	if (principalInfo == null)
		principalInfo = SPUtility.ResolveWindowsPrincipal(
			web.Site.WebApplication, user.LookupValue, SPPrincipalType.All, false);

	SPPrincipal principal;
	if (principalInfo != null)
		principal = web.EnsureUser(user.User.LoginName);
	else
		principal = web.SiteGroups[user.LookupValue];
	return principal;
}

Passing the result from a dialog to the parent page

Sometimes you want to communicate with the parent page of your dialog in order to pass on the result of your work. Thankfully the SP.UI.ModalDialog framework contains the commonModalDialogClose method. The javascript CSOM has all the tools in place to construct the following application:

  • Add a button to the ribbon. This button will also open the dialog.
  • Create an application page that will be used as a dialog. This application page has a text box and a button.
  • Add javascript to capture the result of the application page, in this case just text, and put it where our cursor resides on the parent page.

First things first, we’ll add a button to our ribbon.

ribbonbutton

Since there are many tutorials available, I won’t go into detail here. Simply add an empty element to your project and replace the contents with this one:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <CustomAction
    Id="bramnuyts.InsertButton"
    Location="CommandUI.Ribbon"
    Title="InsertRibbon Button">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Groups._children">
          <Group
            Id="Ribbon.EditingTools.CPInsert.InsertGroup"
            Sequence="1"
            Description="Insert"
            Title="Insert"
            Template="Ribbon.Templates.Flexible2"
            >
            <Controls Id="Ribbon.EditingTools.CPInsert.InsertGroup.Controls">
              <Button Id="Ribbon.EditingTools.CPInsert.InsertGroup.InsertButton"
                Command="InsertCommand"
                Image16by16="/_layouts/images/removeStylesHS.png"
                Image32by32="/_layouts/images/removeStylesHH.png"
                LabelText="Insert"
                TemplateAlias="o2" />
            </Controls>
          </Group>
        </CommandUIDefinition>
        <CommandUIDefinition
          Location="Ribbon.EditingTools.CPInsert.Scaling._children">
          <MaxSize
            Id="Ribbon.EditingTools.CPInsert.InsertGroup.MaxSize"
            Sequence="15"
            GroupId="Ribbon.EditingTools.CPInsert.InsertGroup"
            Size="LargeLarge" />
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler
          Command="InsertCommand"
          CommandAction="javascript:insert();" />
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>
  <CustomAction
    Id="InsertCommand.Script"
    Location="ScriptLink"
    ScriptSrc ="/_layouts/DialogResult/insert.js" />
</Elements>

I added the javascript in a separate javascript file “insert.js”. I like the ScriptLink approach as it doesn’t dirty the elements.xml file. However there are some reported issues when using ScriptLinks so go for the approach you feel is the most safe.

insert.js

Inside my insert.js file I have 2 methods: addElement(txt) and insert(). The “insert()” method will be responsible for handling the click on the button and creating the dialog. The “addElement(txt)” method will be responsible for adding content to the rich content page. Once again SharePoint is there to help us, this time it’s the “RTE” class, which points to the Rich Text Editor. By using the “RTE.Cursor.get_range()” method, we’re able to determine where a user has put his cursor.

function addElement(txt) {
    var range = RTE.Cursor.get_range();
    range.deleteContent();
    var selection = range.parentElement();
    if (!selection) {
        return;
    }
    var span = selection.ownerDocument.createElement('span');
    span.innerText = txt;
    range.insertNode(span);
    RTE.Cursor.get_range().moveToNode(span);
    RTE.Cursor.update();
}

function insert() {
    var options = {
        url: SP.Utilities.Utility.getLayoutsPageUrl('/DialogResult/insert.aspx'),
        title: 'Insert',
        allowMaximize: false,
        showClose: true,
        width: 300,
        height: 150,
        dialogReturnValueCallback: Function.createDelegate(null, function (result, returnValue) {
            if (result == SP.UI.DialogResult.OK) {
                    addElement(returnValue);
            }
        })
    };

    SP.UI.ModalDialog.showModalDialog(options);
}

Now that we have our button and the javascript to handle the dialog, we have to create our dialog page.

insert.aspx

I added a text box and a button to the main contentplaceholder:

insert.aspx.main

Add the “Button1_Click” OnClick method to the button and add the following code to the code behind:

protected void Button1_Click(object sender, EventArgs e)
{
    int result = 1; // Result = OK
    string returnValue = TextBox1.Text;
    Page.Response.Clear();
    Page.Response.Write(String.Format(
        CultureInfo.InvariantCulture, 
        "<script type=\"text/javascript\">window.frameElement.commonModalDialogClose({0}, {1});</script>", 
        new object[] { 
            result, 
            String.IsNullOrEmpty(returnValue) ? "null" : String.Format("\"{0}\"", returnValue) 
        }));
    Page.Response.End();
}

The code will close the dialog window, pass “OK” as result and text from the text box as return value.

When you deploy the code (make sure that your package contains the button) you’ll see the following button appear when you edit your rich content page:

result ribbon

When you click the button the dialog page will appear:

result dialog

When you insert some “test text” and click insert, your text will show up where you left your cursor:

result test text

Lookup Field localization not working when using ContentTypeBinding

Whilst working with lookup fields, I noticed something strange when you separated the Fields and ContentType from your list and you try to use localization. Separating your content type and fields from your list basically means that you no longer work with a custom list definition, but you use ContentTypeBinding on a list instance to create your list.

Here’s an example on how the project is set up:

lookupFieldLocalizationProject

The red arrows point to the lookup list where I use the ContentType “LookUpContentType”, where a lookup field is referenced. This lookup field resides in the “Fields” element:

“Fields” Elements.xml

<?<span class="hiddenSpellError">xml version="1.0" encoding="utf-8"?>
xmlns="http://schemas.microsoft.com/sharepoint/">
  <Field Name="LookUpFieldInElements"
  Required="TRUE"
  ID="{32A4FE06-2745-40D0-B92C-76A58B16C7B0}"
  Type="Lookup"
  Mult="FALSE"
  Overwrite="TRUE"
  List="Lists/Source"
  ShowField="Title"
DisplayName="$Resources:lookupfield,LookUp;"
  StaticName="LookUpFieldInElements"
  />
</Elements>

“LookUpContentType” Elements.xml

<?xml version="1.0" encoding="utf-8"?>
xmlns="http://schemas.microsoft.com/sharepoint/">
<!-- Parent <span class="hiddenSpellError" pre="Parent ">ContentType</span>: Item (0x01) -->
<ContentType ID="0x01002846E06F521348B79F631D4D9E6A07CC" Name="LookUpContentType" Group="Custom Content Types" Description="My Content Type" Inherits="TRUE" Version="0">
    <FieldRefs>
<FieldRef ID="{32A4FE06-2745-40D0-B92C-76A58B16C7B0}" Name="LookUpFieldInElements" Required="TRUE" />
    </FieldRefs>
ContentType>
</Elements>

“LookUpListCTInstance” Elements.xml

<?xml version="1.0" encoding="utf-8"?>
xmlns="http://schemas.microsoft.com/sharepoint/">
<ListInstance
Title="LookUpListCT"
    OnQuickLaunch="TRUE"
    TemplateType="100"
    Url="Lists/LookUpListCT"
Description="My List Instance with ContentTypeBinding"
    FeatureId="00bfea71-de22-43b2-a848-c05709900100">
  </ListInstance>
<ContentTypeBinding ContentTypeId="0x01002846E06F521348B79F631D4D9E6A07CC" ListUrl="Lists/LookupListCT" />
</Elements>

The blue arrows point to a regular list definition which has a list instance included.

The Schema.xml of the “LookupList” list definition

<ContentTypes>
      <ContentType ID="0x0100468fd589c1da4592929a6768ac144020" Name="ListFieldsContentType">
        <FieldRefs>
<FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" />
<FieldRef ID="{a51cabb2-7594-41db-8523-ea5de71e6202}" Name="LookUp" />
        </FieldRefs>
      </ContentType>
    </ContentTypes>
    <Fields>
      <Field
        Name="LookUp"
        ID="{a51cabb2-7594-41db-8523-ea5de71e6202}"
DisplayName="$Resources:lookupfield,LookUp;"
        Type="Lookup"
        List="Lists/Source"
        ShowField="Title" />

When you take a look at the DisplayNames of both the LookupFields residing in Schema.xml and “Fields” Elements.xml, you’ll see that the value holds “$Resources:lookupfield,LookUp;”. So we’re trying to localize the LookupField’s column name.

I’ve added the following resource files where I can add my translations:

lookupFieldLocalizationResourceFiles

lookupfield.resx

<data name="LookUp" xml:space="preserve">
  <value>LookUp</value>
</data>

lookupfield.en-US.resx

<data name="LookUp" xml:space="preserve">
  <value>[ENG] LookUp</value>
</data>

lookupfield.nl-NL.resx

<data name="LookUp" xml:space="preserve">
  <value>[NL] LookUp</value>
</data>

lookupfield.fr-FR.resx

<data name="LookUp" xml:space="preserve">
  <value>[FR] LookUp</value>
</data>

Looks great! Let’s check out the result on our SharePoint lists:

lookupFieldLocalizationDutchLocalization

lookupFieldLocalizationDutchLocalizationCT

Apparently there’s a bug when separating your LookupField from your Schema.xml where it will only use the English localization. So whenever you want to use LookupFields in custom lists, use the list definition way!

Community Day 2012 – I’m a speaker

ComDay2012-imaspeakerThe Belgian community is organizing the 6th edition of Community Day! Community Day 2012 will take place on June 21st 2012 in Utopolis Mechelen. Thanks to the sponsors, Community Day 2012 is a FREE event! I’ll be speaking about future SharePoint development.

During the last years, we notice that all kind of cloud services are taking off. Next to the Infrastructure and Platform as a Service, we also have Software as a Service with specifically for SharePoint: Office 365 and SharePoint Online. While having deployed multiple solutions to the (Private as well as Public) cloud, Devoteam noticed that many of the (commercial and custom developed) solutions, are not ready for cloud deployment. Devoteam already assisted multiple customers in adapting their SharePoint solution for this migration. An even better solution would be that by design the software is ready for all platforms, also those you only will use in the future.
This session will cover many of the discovered problems and will demonstrate how to overcome them. At the end of the session, you will notice that, without extra investments, it is possible to develop solutions that deploy on a private farm as well as into the cloud when respecting only a few design rules.

Automate your SharePoint farm maintenance using Health Rules

By default, SharePoint comes with a broad set of health rules and remedies which check your farm periodically. These, however, don’t always cover specific situations. For instance, your governance document may state that there may only be 10 site collections in one content database. When there are multiple administrators, it’s good practice to implement some sort of extra check, and if possible fix the error. If a newly created content database doesn’t have any limitations set, it’s good to trigger a warning or error for all of your administrators to read.

Start out by creating an empty SharePoint project. I name the solution below “CustomHealthRules”. Deploy the solution as a farm solution.

new sharepoint project customhealthrules

After the empty project is loaded, add a new class named “CheckContentdbs.cs” and inherit SPHealthAnalysisRule.

customhealthrules add new class checkcontentdbs

using Microsoft.SharePoint.Administration.Health;
using Microsoft.SharePoint.Administration;
using System.Collections.Generic;
using System.Text;

namespace CustomHealthRules
{
    class CheckContentdbs : SPHealthAnalysisRule
    {
    }
}

Because we derive from the SPHealthAnalysisRule, we override a few properties to identify our custom rule.

class CheckContentdbs : SPHealthAnalysisRule
{
    public override SPHealthCategory Category
    {
        // Categorise the health rule under configuration in the health rules definition list
        get { return SPHealthCategory.Configuration; }
    }
    public override SPHealthCheckErrorLevel ErrorLevel
    {
        // Define the health as error when the check fails
        get { return SPHealthCheckErrorLevel.Error; }
    }
    public override string Summary
    {
        // Summary of the rule
        get { return "Contentdatabases are misconfigured"; }
    }
}

Before we can make the rule pass or fail, we have to check something. Override the Check() method and identify any problems. Keep track of faulty contentdatabases in a list.

List<SPContentDatabase> faultyContentDbs = new List<SPContentDatabase>();

public override SPHealthCheckStatus Check()
{
    // loop through all web applications
    SPServiceCollection services = SPFarm.Local.Services;
    foreach (SPService curService in services)
    {
        if (curService is SPWebService)
        {
            SPWebService webService = (SPWebService)curService;
            foreach (SPWebApplication webApp in webService.WebApplications)
            {
                // for every web application, check content databases
                foreach (SPContentDatabase contentDB in webApp.ContentDatabases)
                {
                    if (contentDB.MaximumSiteCount != 10)
                    {
                        faultyContentDbs.Add(contentDB);
                    }
                }
            }
        }
    }
    if (faultyContentDbs.Count == 0)
        return SPHealthCheckStatus.Passed;
    else
        return SPHealthCheckStatus.Failed;
}

Whenever your check fails, you must give a remedy to your administrators.

public override string Remedy
{
    get { return "Set the warning and maximum site collection limit on the content database"; }
}

public override string Explanation
{
    get
    {
        if (faultyContentDbs.Count > 0)
        {
            StringBuilder sBuild = new StringBuilder();
            sBuild.Append("The following databases are incosistent:\n");
            foreach (SPContentDatabase contentDB in faultyContentDbs)
            {
                sBuild.AppendFormat("db {0} on webapp {1}\n", contentDB.Name, contentDB.WebApplication.Name);
            }
            return sBuild.ToString();
        }
        else
            return "No faulty contentdatabases have been found.";
    }
}

Finally, add some default configuration settings. You may also choose to manually configure your health rule. In that case you don’t need to override this property.

public override SPHealthAnalysisRuleAutomaticExecutionParameters AutomaticExecutionParameters
{
    get
    {
        SPHealthAnalysisRuleAutomaticExecutionParameters retval = new SPHealthAnalysisRuleAutomaticExecutionParameters();
        retval.Schedule = SPHealthCheckSchedule.Hourly;
        retval.Scope = SPHealthCheckScope.All;
        retval.ServiceType = typeof(SPTimerService);
        retval.RepairAutomatically = false;
        return retval;
    }
}

Our CheckContentdbs class file is complete. All that remains is to register the rule inside a farm feature. Add a new feature to your project, name it and add a description. You’ll see that you can’t just select your codefile in the project. Registering a health rule is slightly different than your average web part. Add a new feature EventReceiver and add the code you find on MSDN. Make sure you Resolve unadded Usings.

customhealthrules farm feature

customhealthrules feature event receiver

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    Assembly a = Assembly.GetExecutingAssembly();
    IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.RegisterRules(a);

    if (exceptions != null)
    {
        string logEntry = a.FullName;
        if (exceptions.Count == 0)
        {
            logEntry += " All rules were registered.";
        }
        else
        {
            foreach (KeyValuePair<Type, Exception> pair in exceptions)
            {
                logEntry += string.Format(" Registration failed for type {0}. {1}",
                                            pair.Key, pair.Value.Message);
            }
        }
        System.Diagnostics.Trace.WriteLine(logEntry);
    }
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
    Assembly a = Assembly.GetExecutingAssembly();
    IDictionary<Type, Exception> exceptions = SPHealthAnalyzer.UnregisterRules(a);

    if (exceptions != null)
    {
        string logEntry = a.FullName;
        if (exceptions.Count == 0)
        {
            logEntry += " All rules were unregistered.";
        }
        else
        {
            foreach (KeyValuePair<Type, Exception> pair in exceptions)
            {
                logEntry += string.Format(" Unregistration failed for type {0}. {1}",
                                            pair.Key, pair.Value.Message);
            }
        }
        System.Diagnostics.Trace.WriteLine(logEntry);
    }
}

Deploy the solution. When you activate the farm feature in your central admin, you can check the health rules definition list (under Monitoring) and you’ll see your custom health rule is successfuly deployed!

customhealthrules registered rule

After the rule is ran, you’ll get an error in your health reports where you can check for faulty content databases.

customhealthrules rule triggered error

So what’s next? If you wish to debug your solution, remember to attach your debugger to the OWSTIMER.EXE. Health rules are timer jobs after all.

Also check the SPRepairableHealthAnalysisRule class. If you derive from this one, you can override the Repair() method where you can implement default “fixes” for your health rule.

You can also implement some Health Rule configuration for your Check() method. Think of propertybags for instance. You can put your preferred default values in the feature activated method.