blog.scoreman.net

Setting Site Collection to ReadOnly through CSOM

April 21st, 2015

PIf working with SharePoint Online we have the ability to set LockState in order to set a site collection to read only. Unfortunately this is currently not available on-premises. Instead we can use Site Policies to apply a read only state to the site collection. In order to do this, create a Site Policy with the setting “Do not close or delete site automatically.” and “The site collection will be read only when it is closed.”. If you want to set read only to several site collections you can create this policy in the content type hub and have the policy published to all site collections. Once the policy exist in the site collection you can run the following CSOM code to set the site as read only:

 

clientContext.Web.ApplySitePolicy("PolicyName");
ProjectPolicy.CloseProject(clientContext, clientContext.Web);
clientContext.ExecuteQuery();

ApplySitePolicy is an extension method in OfficeDevPnP.

readonly

Week numbers in calendar view – revisited for SharePoint Online

September 9th, 2014

A long time ago I wrote some posts on how you could add week numbers to the monthly calendar view both in SharePoint 2007 and in SharePoint 2010. The solution in SP2007 depended on overriding the calendar’s control template using server side code. Since full trust code is no option in SharePoint Online that does not work. The solution in SP2010 used client side JavaScript but since the calendar view is totally rewritten in SP2013 the SP2010 solution does not work either. So I thought that I would revisit this topic once again for a Office 365/SharePoint Online/SharePoint 2013 version.

The code basically has two main parts. First we need to intercept the client side calls that the calendar view does in SharePoint 2013. Thanks to this post for showing how to do that.

Contoso.WeekNumber.InterceptCalendarEvent = function () {
    ExecuteOrDelayUntilScriptLoaded(function () {
        var onItemsSucceed = SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed;
        SP.UI.ApplicationPages.CalendarStateHandler.prototype.onItemsSucceed = function ($p0, $p1) {
            onItemsSucceed.call(this, $p0, $p1);
            Contoso.WeekNumber.AddWeekNumbers();
        };
    }, "SP.UI.ApplicationPages.Calendar.js");
}

Secondly using JQuery we can find all TH elements and add week numbers.

Contoso.WeekNumber.AddWeekNumbers = function () {
    $(".ms-acal-month > TBODY > TR > TH[evtid='week']").each(function () {
        var firstDay = new Date($(this).attr("date"));
        if (firstDay.toString() != "NaN" && firstDay.toString() != "Invalid Date") {
            var week = firstDay.getWeek(firstDay.getDay());
            week = (week.toString().length == 1) ? '0' + week.toString() : week.toString();
            $(this).html("<div class='ms-picker-weekbox'><acronym title='Week number " + week + "'>" + week + "</acronym></div>");
            $(this).attr("class", "ms-picker-week");
            $(this).css("vertical-align", "middle");
        }
    });
}

For calculating week numbers I used the same JavaScript function that I used in the post on how to add week numbers in SharePoint 2010. It calculates week numbers based on the Gregorian calendar but you can replace that function with whatever you want.

I’ve attached a working solution that is ready to deploy to Office 365. Run the solution from Visual Studio, trust the app and then click on the init button. If all goes well JQuery and a custom javascript file will be added to the site assets library. Also custom actions will be added to the host web so that the scripts are included during page load. This example also works with minimum download strategy (MDS) turned on. The end result looks like this:

SharePointContext null in postback

February 24th, 2014

If you are experiencing issues with the SharePointContext class returning NULL when you call SharePointContextProvider.Current.GetSharePointContext(HttpContext) in a postback you may what to check that the context is initialized on the first page load. This can be done for example by calling GetSharePointContext in Page Load event or SharePointContextProvider.CheckRedirectionStatus in Page PreInit.

The reason for this is that the SharePointContext class is dependent on parameters (AppContext, AppContextToken, AccessToken and SPAppToken) being in the request as form or querystring parameters. If present SharePointContext will store values in session and cookie for use in future postbacks. Therefor if SharePointContext is not used at all on initial load all dependent parameters will get lost in postback.

It’s pretty easy to reproduce this, just create a new provider-hosted app using the ASP.NET MVC Form Application template. Remove any code in Page_PreInit and move the example code in Page_Load to a button click event that fires on postback. Debug application and notice that GetSharePointContext(HttpContext) returns NULL in button click event.

Get term store in SharePoint Online

February 13th, 2014

This is just ta small tips when working with taxonomy client-side object model and SharePoint Online. You will find lots of examples like the one below online:

var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
var termStores = taxSession.get_termStores();
var termStore = termStores.getByName("Taxonomy_Dmxzz8tIBzk8wNVKQpJ+xA==");

The important thing to note is that the name of the term store (something like Taxonomy_Dmxzz8tIBzk8wNVKQpJ+xA==) can and will change over time. This can happen if the service (Office 365) creates a new Managed Metadata Service Application or even fails over to another data center. To prevent the code from crashing a better approach could be to use getDefaultSiteCollectionTermStore:

var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
var termStores = taxSession.get_termStores();
var termStore = taxSession.getDefaultSiteCollectionTermStore();

Preserving ClientId and ClientSecret in Windows Azure

January 10th, 2014

Here is a quick tips if you are building SharePoint Provider Hosted Apps in Windows Azure. If there are several developers publishing an app (or if you are using publishing in Visual Studio Online) to Windows Azure chances are that ClientId and ClientSecret will be changed in the web.config file. When ClientId and ClientSecret are changed the app will no longer be able to communicate with SharePoint unless you register the app again with the new ClientId and ClientSecret.

Another way to solve this would be to prevent the ClientId and ClientSecret to change. This can be done by overriding what is in the web.config file using built in functionality in Azure. If you set appsettings in the Azure Portal or using solution explorer in Visual Studio these settings will override what is in the web.config file. This way your ClientId and ClientSecret will match what was in the app manifest when the app was registered in SharePoint.

Setting appSettings through the Windows Azure Portal

Setting appSettings through the Windows Azure Portal

Setting appSettings through solution explorer in Visual Studio

Setting appSettings through solution explorer in Visual Studio

InfoPath error after installing SharePoint 2010 Service Pack 2

October 8th, 2013

Interesting discovery yesterday that I thought I should share. After installing SharePoint 2010 Service pack 2 a customer ran into problems with their InfoPath forms. The generic error message was “The custom code in the form cannot be run. This functionality may be deactivated on the server”. Looking in the ULS log there were some more information:

Microsoft.SharePoint.UserCode.SPUserCodeSolutionProxiedException: Could not load type 'Microsoft.SharePoint.Administration.SPService' from assembly 'Microsoft.SharePoint, Version=14.900.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'.    Server stack trace:     
 at Microsoft.Office.InfoPath.Server.DocumentLifetime.Document.QueryDataOnLoad(DataAdapter documentDataAdapter, DataObject documentDataObject)    
 at Microsoft.Office.InfoPath.Server.DocumentLifetime.Document.ExecuteInitOnLoadForDataObjects()    
 at Microsoft.Office.InfoPath.Server.DocumentLifetime.Document.LoadFromRemoteContext(Boolean firstOpened, String editingSessionId, Solution solution, Byte[] serializedState, Byte[] serializedVersionState, Dictionary`2 inputParams, OpenParameters openParameters)

After a lot of trial and errors I did some reflection on the Microsoft.Office.InfoPath.Server.DocumentLifetime.Document.QueryDataOnLoad method in Microsoft.Office.InfoPath.Server.dll. The method had a small code change between the Cumulative Update the customer was on and Service Pack 2.

Before Service Pack 2

Before Service Pack 2

After Service Pack 2

After Service Pack 2

The small code change, FormsService formsService = FormsService.TryGetLocal(), uses Microsoft.SharePoint.Administration.SPService. The problem occurs when this code is run from within the sandbox process. Then SPService is not available just like the error message said. QueryDataOnLoad gets called if you have an InfoPath form with data connections that loads on startup.

It is really easy to reproduce this bug. Just install SP2, create an InfoPath form with a data connection (that loads a SharePoint list at startup) and also add code-behind for the loading event. Fortunately the line of code causing the problem is removed in August 2013 Cumulative Update.

Microsoft.Office.Server.Search.Administration exceptions in Event Log

August 22nd, 2013

If you see any of the exceptions below in the Event Viewer on your SharePoint farm make sure that you have check “This service application is the default storage location for Keywords” and “This service application is the default storage location for column specific term sets” on the Managed Metadata Service Application Proxy in your farm. There are some posts online saying that the search service account or search crawl account needs read permission on the Managed Metadata Service Application but that was not needed in my SharePoint 2013 environment.

Error message 1

The Execute method of job definition Microsoft.Office.Server.Search.Administration.CustomDictionaryDeploymentJobDefinition (ID <GUID>) threw an exception. More information is included below.

Failed to run flow Microsoft.CustomDictionaryDeployment. Correlation Id: <GUID>.

Error message 2:

The Execute method of job definition Microsoft.Office.Server.Search.Administration.QueryClassificationDictionaryUpdateTimerJobDefinition (ID <GUID>) threw an exception. More information is included below.

Unable to locate Managed Metadata Proxy which is default keyword taxonomy for SSA <GUID>

Aggregating tasks using SharePoint Search JavaScript Object Model

August 21st, 2013

This is a follow up post on my previous post where I showed how you could use search and REST to aggregate a user’s tasks. This example will show the same thing except I use the JavaScript Client Object Model. The example is written as a SharePoint-hosted app using Visual Studio 2012 Update 3.

For this example to Work you will have to add a reference to sp.search.js like below or you could use Script on Demand to load it JavaScript code.

<script type="text/javascript" src="/_layouts/15/sp.search.js"></script>

For simplicity all error handling has been removed from the example code. Here is how the code in App.js looks like:

'use strict';

var context = SP.ClientContext.get_current();

$(document).ready(function () {
    getCurrentUserDisplayName().then(getUserTasks).then(displayResult);
});

function getCurrentUserDisplayName() {
    var deferred = jQuery.Deferred();
    var user = context.get_web().get_currentUser();

    context.load(user);
    context.executeQueryAsync(function () { deferred.resolve(user); });

    return deferred.promise();
}

function getUserTasks(user) {
    var deferred = jQuery.Deferred();
    var currentUserDisplayName = user.get_title();

    var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(context);
    keywordQuery.set_queryText("ContentTypeId:0x0108* NOT Status:Completed AND AssignedTo:" + currentUserDisplayName);
    keywordQuery.set
    var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(context);
    var results = searchExecutor.executeQuery(keywordQuery);

    context.executeQueryAsync(function () { deferred.resolve(results); });

    return deferred.promise();
}

function displayResult(results) {
    var viewModel = function () {
        var self = this;
        self.tasks = ko.observableArray();

        jQuery.each(results.m_value.ResultTables[0].ResultRows, function () {
            self.tasks.push({
                title: ko.observable(this.Title),
                url: ko.observable(this.Path)
            });
        });
    };

    ko.applyBindings(new viewModel());
}

Aggregating tasks using SharePoint Search REST API

August 20th, 2013

This is a short example of how you can use SharePoint Search REST Search to aggregate all tasks assigned to the current user that are not set to completed. The example is a SharePoint-hosted app that makes two REST calls. The first one is for retrieving the full name of the user currently logged in. The second call is to the search API and executes the query. For this app to Work you will have to add QueryAsUserIgnoreAppPrincipal permisson.

For the filtering of all tasks, except the ones with status set to completed, to Work the crawled properties ows_Status will have to be added to the managed property Status. If you want to list all tasks regardless of status just remove “NOT Status:Completed”.

'use strict';

jQuery(document).ready(function () {
    getCurrentUserDisplayName().then(getUserTasks).then(displayResult);
});

function getCurrentUserDisplayName() {
    var url = _spPageContextInfo.webAbsoluteUrl + "/_api/web/currentuser";
    return jQuery.ajax(url, { method: "GET", headers: { "accept": "application/json;odata=verbose" } });
}

function getUserTasks(data) {
    var currentUserDisplayName = data.d.Title;
    var url = _spPageContextInfo.webAbsoluteUrl + "/_api/search/query?selectproperties='Title,Path'&querytext='ContentTypeId:0x0108* NOT Status:Completed AND AssignedTo:" + currentUserDisplayName + "'";
    return jQuery.ajax(url, { method: "GET", headers: { "accept": "application/json;odata=verbose" } });
}

function displayResult(data) {
    var viewModel = function () {
        var self = this;
        self.tasks = ko.observableArray();

        jQuery.each(data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results, function () {
            self.tasks.push({
                title: ko.observable(getValueByKey('Title', this.Cells.results)),
                url: ko.observable(getValueByKey('Path', this.Cells.results))
            });
        });
    };

    ko.applyBindings(new viewModel());
}

function getValueByKey(key, results) {
    var postItem = jQuery.grep(results, function (e) {
        if (e.Key === key)
            return e;
    })[0].Value;

    return postItem;
}

In displayResult I’m using Knockout JavaScript library to bind the result to my UI but you are free to do whatever you you want with the result. Thanks to Cas van Lersel for the helper function getValueByKey that parses the key value pairs that are returned by SharePoint.

Unhandled Exception when setting Individual Quota on Site Collection

March 9th, 2013

Recently I ran in to an interesting situation at a customer. We were setting up a new SharePoint Server 2013 farm and I was showing the customer how to set storage quotas on site collection when we ran into an unhandled exception. The exception occurred when choosing Individual Quota in the dropdown menu under Current quota template. Looking at the correlation id in the ULS log we found the following error messages:

Application error when access /_admin/sitequota.aspx, Error=Object reference not set to an instance of an object.
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.SetFormValues()
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.QTIndexChanged(Object sender, EventArgs e)
     at System.Web.UI.WebControls.ListControl.OnSelectedIndexChanged(EventArgs e)
     at System.Web.UI.Page.RaiseChangedEvents()
     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

System.NullReferenceException: Object reference not set to an instance of an object.
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.SetFormValues()
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.QTIndexChanged(Object sender, EventArgs e)
     at System.Web.UI.WebControls.ListControl.OnSelectedIndexChanged(EventArgs e)
     at System.Web.UI.Page.RaiseChangedEvents()
     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Getting Error Message for Exception System.Web.HttpUnhandledException (0x80004005): Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.NullReferenceException: Object reference not set to an instance of an object.
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.SetFormValues()
     at Microsoft.SharePoint.ApplicationPages.SiteQuotaPage.QTIndexChanged(Object sender, EventArgs e)
     at System.Web.UI.WebControls.ListControl.OnSelectedIndexChanged(EventArgs e)
     at System.Web.UI.Page.RaiseChangedEvents()
     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
     at System.Web.UI.Page.HandleError(Exception e)
     at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
     at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
     at System.Web.UI.Page.ProcessRequest()
     at System.Web.UI.Page.ProcessRequest(HttpContext context)
     at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
     at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Unexpected error occurred in method 'Put' , usage 'SPViewStateCache' - Exception 'Microsoft.ApplicationServer.Caching.DataCacheException: ErrorCode:SubStatus:The request timed out.. Additional Information : The client was trying to communicate with the server : net.tcp://servername.company.se:22233
     at Microsoft.ApplicationServer.Caching.DataCache.ThrowException(ResponseBody respBody, RequestBody reqBody)
     at Microsoft.ApplicationServer.Caching.DataCache.InternalPut(String key, Object value, DataCacheItemVersion oldVersion, TimeSpan timeout, DataCacheTag[] tags, String region, IMonitoringListener listener)
     at Microsoft.ApplicationServer.Caching.DataCache.<>c__DisplayClass25.b__24()
     at Microsoft.ApplicationServer.Caching.DataCache.Put(String key, Object value, TimeSpan timeout)
     at Microsoft.SharePoint.DistributedCaching.SPDistributedCache.Put(String key, Object value)'.

The stack trace mentions distributed cache and at first we thought we had some issue where our front end servers could not communicated with the cache servers. The farm topology looked like this:

It turned out to be something totally different causing the problem. The customer had installed the Swedish version of SharePoint Server 2013. Because Central Administration can be hard to navigate on Swedish if you are used to the English administration UI we had installed the English language pack and set that as preferred language. When going back to the Swedish UI in Central Administration the error went away. I verified this in my virtual environment with the English installation of SharePoint Server 2013. After I installed German language pack (I choose German because Swedish was not yet available) and changed preferred UI I could reproduce the error. Guess you should stick with the same language in Central Administration as your installation language if you want to set individual quota on site collections.

A very far-fetched bug and solution as well.