Wednesday, February 18, 2015

SharePoint Online and AngularJS

Article Source Please choose 'View Source' in your browser to view the HTML, or File | Save to save this file to your hard drive for editing.

Introduction

When I had some downtime, I wanted to create a sample using AngularJS framework to display list items from a SharePoint Online site.  I have seen some of the angular demonstrations and I was drawn to the idea of breaking up javascript into self contained modules that can be unit tested.  The result is a very clean easy to read and maintain javascript code base.
This article will touch on some of the nuances I found when using AngularJS with Sharepoint and Sharepoint Online.

Background

There are a few things I found with AngularJS framework which is very different from other javascript frameworks.  The learning curve.  It is very easy to write an Angular application with very little knowledge of the framework unlike BackBone.js.  However, it very hard to write a hard core serious Angular application.  The documentation is either incomplete or lacking.  Don't be frustrated, just keep on plugging away.

Using the code

AngularJS Service

Creation of the service

Here we create the service within the SharePointJSOMService.js file and grab the user information from sharepoint using a rest call
myApp.service('SharePointJSOMService', function ($q, $http) {
    this.getCurrentUser = function () {
        var deferred = $.Deferred();
        //First we must call the EnsureSetup method
        JSRequest.EnsureSetup();
        hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
        appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);

        var userid = _spPageContextInfo.userId;
        var restQueryUrl = appweburl + "/_api/web/getuserbyid(" + userid + ")";

        var executor = new SP.RequestExecutor(appweburl);
        executor.executeAsync({
            url: restQueryUrl,
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data, textStatus, xhr) {
                deferred.resolve(JSON.parse(data.body));
            },
            error: function (xhr, textStatus, errorThrown) {
                deferred.reject(JSON.stringify(xhr));
            }
        });
        return deferred;
    };

Creation of the list in the Host Web

The following function will grab the context of the host web and create a list.  Remember, you have to specify the application to have access to the host web in your AppMainifest.xml file.
this.createList = function ($scope, listTitle) {
        var deferred = $.Deferred();
        //First we must call the EnsureSetup method
        var hostUrl = decodeURIComponent(getQueryStringParameter("SPHostUrl"));


        var currentcontext = new SP.ClientContext.get_current();
        var hostcontext = new SP.AppContextSite(currentcontext, hostUrl);
        var hostweb = hostcontext.get_web();

        //check list exists
        var lists = hostweb.get_lists();
        currentcontext.load(lists);
        currentcontext.executeQueryAsync(
            function () {
                var isListAvail = false;
                var listEnumerator = lists.getEnumerator();
                while (listEnumerator.moveNext()) {
                    list = listEnumerator.get_current();
                    if (list.get_title() == listTitle) {
                        isListAvail = true;
                        deferred.done("success list already exists");
                    }
                }
                if (isListAvail != true) {
                    //create the list
                    var listCreationInfo = new SP.ListCreationInformation();
                    listCreationInfo.set_title(listTitle);
                    listCreationInfo.set_templateType(SP.ListTemplateType.tasks);
                    var list = hostweb.get_lists().add(listCreationInfo);
                    currentcontext.load(list);
                    currentcontext.executeQueryAsync(
                        function () {
                            deferred.done("created list");
                        },
                        function (sender, args) {
                            deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
                        }
                    );
                }
            },
            function (sender, args) {
                deferred.reject('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
            }
        );

        return deferred;
    };

Retrieval of the List

The following function will use a REST call to retrieve information from the list within the hostweb
 this.getTasksREST = function ($scope, listTitle) {
        var deferred = $.Deferred();
        //First we must call the EnsureSetup method
        JSRequest.EnsureSetup();
        hostweburl = decodeURIComponent(JSRequest.QueryString["SPHostUrl"]);
        appweburl = decodeURIComponent(JSRequest.QueryString["SPAppWebUrl"]);

        var restQueryUrl = appweburl + "/_api/SP.AppContextSite(@target)/web/lists/getByTitle('" + listTitle + "')/items?$select=Title,ID,DueDate,Status,Priority&@target='" + hostweburl + "'";

        var executor = new SP.RequestExecutor(appweburl);
        executor.executeAsync({
            url: restQueryUrl,
            method: "GET",
            headers: { "Accept": "application/json; odata=verbose" },
            success: function (data, textStatus, xhr) {
                deferred.resolve(JSON.parse(data.body));
            },
            error: function (xhr, textStatus, errorThrown) {
                deferred.reject(JSON.stringify(xhr));
            }
        });
        return deferred;
    };

AngularJS Controller

Creation of the controller

The creation of the controller by passing in the scope and SharePointJSOMService objects
myApp.controller('ListCtrl', ['$scope', 'SharePointJSOMService', function ($scope, SharePointJSOMService) {
    SP.SOD.executeOrDelayUntilScriptLoaded(runDelayedCode, "SP.js");
    function runDelayedCode() {
        $scope.tasks = [];

        // grab current user from web service
        // place it in scope
       $.when(SharePointJSOMService.getCurrentUser())
          .done(function (jsonObject) {
              currentUser = jsonObject.d;
              $scope.currentUser = currentUser;
              //scope was not updated so we need to push it in
              if (!$scope.$$phase) { $scope.$apply(); }
          })
          .fail(function (err) {
              console.info(JSON.stringify(err));
          });

Default.aspx Page

The default.aspx will create the AngularJS application and bind to the list items.  It is a very clean binding.
   <div ng-app="myApp">

           <div ng-controller="ListCtrl">

          
           <div>
               Find current user from _api/webs/getuserbyid using angular service
               <h1>Welcome: {{currentUser.Title}}</h1>
           </div>

                <ul class="unstyled">
               <li class="row" ng-repeat="task in tasks">
                    <input type="checkbox" ng-model="task.done" />
                    <span class="done-{{todo.done}}">{{task.text}}</span>
               </li>
                    </ul>
           

       </div>

       
   
   </div>

App.js

The App.js file will set up some global variables for use in the default.aspx page
'use strict';
var TaskListName = 'testlist';
var myApp = angular.module('myApp', ['ui.bootstrap']);

var hostweburl;
var appweburl;
var currentUser;

Points of Interest

Some examples loaded the sharepoint js files by using the following tags in the default.aspx page
<SharePoint:ScriptLink name="sp.js" runat="server" LoadAfterUI="true" Localizable="false" />    
<SharePoint:ScriptLink name="sp.runtime.js" runat="server" LoadAfterUI="true" Localizable="false" />
I found that had the side effect of inconsitent list creation in the host web.  The error was "Unexpected Response From Server" with a null stack trace.  To fix the issue, I went with the following javascript includes
<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.js"></script>
 or within my javascript
$.getScript(scriptbase + "SP.Runtime.js",
            function () {
                $.getScript(scriptbase + "SP.js",
                    function () {
                        $.getScript(scriptbase + "SP.RequestExecutor.js", 
                             function () {

History

Version 1, just binding

Monday, February 16, 2015

Finised MCSD Sharepoint 2013

Forgot to post since MicroLink has been merging with HP but I am officially a Sharepoint 2013 MCSD.

Now onto Amazon Cloud  lol

Tuesday, April 29, 2014

Monday, April 7, 2014

How to find a user's effective rights on a file

A client wanted a handler to figure out the current user's effective rights. I could not impersonate the user, so we needed to check the user's rights. Here is the class I am using to do it.



 public static class FileSystemRightsEx
    {
        public static bool HasRights(this FileSystemRights rights, FileSystemRights testRights)
        {
            return (rights & testRights) == testRights;
        }
    }


    public static class FileSystemEffectiveRights
    {
        public static FileSystemRights GetRights(string userName, string path)
        {
            ULSLoggingService.LogUITrace("Trying to get rights for the path.");

            if (string.IsNullOrEmpty(userName))
            {
                throw new ArgumentException("userName");
            }

            if (!Directory.Exists(path) && !File.Exists(path))
            {
                throw new ArgumentException(string.Format("path:  {0}", path));
            }

            return GetEffectiveRights(userName, path);
        }



        /// <summary>
        /// based on the rules retrieved figure out if the user has acces
        /// </summary>
        /// <param name="userName">user name no domain</param>
        /// <param name="path">file share path</param>
        /// <returns></returns>
        private static FileSystemRights GetEffectiveRights(string userName, string path)
        {
            FileSystemAccessRule[] accessRules = GetAccessRulesArray(userName, path);
            FileSystemRights denyRights = 0;
            FileSystemRights allowRights = 0;

            for (int index = 0, total = accessRules.Length; index < total; index++)
            {
                FileSystemAccessRule rule = accessRules[index];

                if (rule.AccessControlType == AccessControlType.Deny)
                {
                    denyRights |= rule.FileSystemRights;
                }
                else
                {
                    allowRights |= rule.FileSystemRights;
                }
            }

            return (allowRights | denyRights) ^ denyRights;
        }

        /// <summary>
        /// Compare the file system access rules with the sids comming from the user
        /// if we might have a deny rule or an allow rule
        /// </summary>
        /// <param name="userName">user name without domain</param>
        /// <param name="path">path to file</param>
        /// <returns></returns>
        private static FileSystemAccessRule[] GetAccessRulesArray(string userName, string path)
        {
            ULSLoggingService.LogUITrace(string.Format("Trying to get access rules array for user '{0}' and path '{1}'.", userName, path));

            // get all access rules for the path - this works for a directory path as well as a file path
            AuthorizationRuleCollection authorizationRules = (new FileInfo(path)).GetAccessControl().GetAccessRules(true, true, typeof(SecurityIdentifier));

            foreach (AuthorizationRule rule in authorizationRules)
            {
                ULSLoggingService.LogUITrace(string.Format("FileSystem Rule name: '{0}'",rule.IdentityReference.Translate(typeof(NTAccount)).Value));
            }

            // get the user's sids
            string[] sids = GetSecurityIdentifierArray(userName);

            // get the access rules filtered by the user's sids
            return (from rule in authorizationRules.Cast<FileSystemAccessRule>()
                    where sids.Contains(rule.IdentityReference.Value)
                    select rule).ToArray();
        }

        /// <summary>
        /// Get the group SIDS of the current user
        /// assumption: that users are unique within the domain
        /// </summary>
        /// <param name="userName">user's name without the domain</param>
        /// <returns>array of sids</returns>
        private static string[] GetSecurityIdentifierArray(string userName)
        {
            // connect to the domain
            PrincipalContext pc = new PrincipalContext(ContextType.Domain);

            // search for the domain user
            UserPrincipal user = new UserPrincipal(pc) { SamAccountName = userName };
            PrincipalSearcher searcher = new PrincipalSearcher { QueryFilter = user };
            user = searcher.FindOne() as UserPrincipal;

            if (user == null)
            {
                throw new ApplicationException(string.Format("Invalid User Name:  {0}", userName));
            }

            // use WindowsIdentity to get the user's groups
            WindowsIdentity windowsIdentity = new WindowsIdentity(user.UserPrincipalName);
            string[] sids = new string[windowsIdentity.Groups.Count + 1];

            sids[0] = windowsIdentity.User.Value;

            for (int index = 1, total = windowsIdentity.Groups.Count; index < total; index++)
            {
                sids[index] = windowsIdentity.Groups[index].Value;
                try
                {
                    ULSLoggingService.LogUITrace("User:" + userName + "User Group:" + windowsIdentity.Groups[index].Translate(typeof(NTAccount)).Value);
                }
                catch (Exception ex)
                {
                    ULSLoggingService.LogUIException("proplem with logging", ex);
                }
            }

            return sids;
        }
    }

Wednesday, February 26, 2014

FQL field search with a question mark '?'

I was trying to return a item by a query to the path

path:string("http://sometext/page.aspx?id=123")

I could search for http://sometext/page.aspx or id=123 but the question mark would cause the query not to find the item.

The solution:
replace the ? with a space.  apparently the ? is a noise word and replaced with a space during indexing.  It is an odd issue, since the ? is show on the qr server.

such is life

Monday, February 24, 2014

Adding Controls to the Additional Page Head in Sharepoint 2010

I wanted to add a control that would add some html to site collection administration pages.  These pages are not directly editable.

I looked at adding controls to the additional page head tag within the master page.  The control in the master page looks like

<asp:ContentPlaceHolder id="PlaceHolderAdditionalPageHead" runat="server"/>

To the project add a new module

edit the elements.xml and add in your control

 <Control Id="AdditionalPageHead" ControlSrc="~/_CONTROLTEMPLATES/yourcontrol" Sequence="15100" xmlns="http://schemas.microsoft.com/sharepoint/" />

The sequence number is just a random number.

Now your control will be added to all pages that use the masterpage.  Just place some conditional logic in your control looking for the site collection addition page and there you have it

Wednesday, February 12, 2014

Windows 2008 R2 and fast search for sharepoint SAM worker dead issue

First few days on the job and I was banging my head against the wall.  I was seeing a communication error  between the content ssa and the sam worker.  In the log files I saw the following message

AdminLibrary.dll:MakeRemoteRequestToWorker - Unable to complete request to sam worker node

But the samworker was running when doing a nctrl status.  But when looking further and doing a

get fastsearchsecurityworkernode

the process was dead

It is caused by a loopback check during authentication in windows 2008r2.  It will happen when you are running fast on a standalone machine.

To fix the issue run

setspn -A http/servername.domain.com domain\username

Live and learn