Friday, December 23, 2016

Get current server date time via javascript object model in Sharepoint

Some time ago I wrote post Get Sharepoint current datetime in javascript for Sharepoint Online, where described the way how to get current server datetime in javascript and how it works (or to be more precise almost current date time – see below for explanation). Briefly we use _spPageContextInfo.clientServerTimeDelta standard variable which is returned from server in the following form:

   1: clientServerTimeDelta: new Date("2015-05-21T16:54:00.0000000Z") - new Date()

In this line of code string is populated from DateTime.Now on server side:

   1: sb.Append("\", clientServerTimeDelta: new Date(\"");
   2: sb.Append(DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture));
   3: sb.Append("\") - new Date()");

So in order to get current server datetime in javascript we need to add current javascript date to _spPageContextInfo.clientServerTimeDelta. In original post I wrote that it can be done like that:

   1: var d = new Date();
   2: var currentServerDateTime = _spPageContextInfo.clientServerTimeDelta + d;

which is not fully correct. In order to have correct datetime object in currentServerDateTime use the following code:

   1: var currentServerDateTime = new Date(new Date().getTime() + _spPageContextInfo.clientServerTimeDelta);

And remember that it will be almost current server date time – difference will be in the time between moments when javascript interprets line with initialization of _spPageContextInfo.clientServerTimeDelta variable (see above) and line where you calculate currentServerDateTime.

Tuesday, December 20, 2016

Inherit global and current navigation settings on the Sharepoint publishing sites from parent web via client object model

Sometimes on Sharepoint publishing site we need to inherit global or current navigation settings from parent site:

In order to do that via client object model the following PowerShell script can be utilized:

   1:  
   2: param(
   3:     [string]$siteUrl,
   4:     [string]$username,
   5:     [string]$password
   6: )
   7:  
   8: $currentDir = Convert-Path(Get-Location)
   9: $dllsDir = resolve-path($currentDir + "\dlls_16_csom_16.1.3912.1204")
  10:  
  11: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  12: "Microsoft.SharePoint.Client.Runtime.dll"))
  13: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  14: "Microsoft.SharePoint.Client.dll"))
  15: [System.Reflection.Assembly]::LoadFile([System.IO.Path]::Combine($dllsDir,
  16: "Microsoft.SharePoint.Client.Publishing.dll"))
  17:  
  18: if (-not $siteUrl)
  19: {
  20:     Write-Host "Specify site url in siteUrl parameter" -foregroundcolor red
  21:     return
  22: }
  23:  
  24: if (-not $username)
  25: {
  26:     Write-Host "Specify user name in username parameter" -foregroundcolor red
  27:     return
  28: }
  29:  
  30: if (-not $password)
  31: {
  32:     Write-Host "Specify user password in password parameter" -foregroundcolor red
  33:     return
  34: }
  35:  
  36: function Inherit-Navigation-For-Web($ctx, $web, $taxSession)
  37: {
  38:     Write-Host "Inherit navigation for" $web.Url -foregroundcolor green
  39:     $ctx.Load($web)
  40:     $ctx.ExecuteQuery()
  41:     
  42:     if ($web.ID -eq $ctx.Site.RootWeb.ID)
  43:     {
  44:         Write-Host "    Skip root web" -foregroundcolor yellow
  45:     }
  46:     else
  47:     {
  48:         $navigationSettings = New-Object Microsoft.SharePoint.Client
  49: .Publishing.Navigation.WebNavigationSettings($ctx, $web)
  50:        
  51:         $navigationSettings.GlovalNavigation.Source = [Microsoft.SharePoint
  52: Publishing.Navigation.StandardNavigationSource]::InheritFromParentWeb 
  53:         $navigationSettings.CurrentNavigation.Source = [Microsoft.SharePoint
  54: .Client.Publishing.Navigation.StandardNavigationSource]::InheritFromParentWeb
  55:         $navigationSettings.Update($taxSession)
  56:         
  57:         Write-Host "    Navigation is updated" -foregroundcolor green
  58:     }
  59:     
  60:     $ctx.Load($web.Webs)
  61:     $ctx.ExecuteQuery()
  62:     $web.Webs | ForEach-Object { Inherit-Navigation-For-Web $ctx $_ $taxSession }
  63: }
  64:  
  65: $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl)
  66: $ctx.RequestTimeOut = 1000 * 60 * 10;
  67: $ctx.AuthenticationMode =
  68: [Microsoft.SharePoint.Client.ClientAuthenticationMode]::Default
  69: $securePassword = ConvertTo-SecureString $password -AsPlainText -Force
  70: $credentials = New-Object Microsoft.SharePoint.Client
  71: .SharePointOnlineCredentials($username, $securePassword)
  72: $ctx.Credentials = $credentials
  73: $ctx.Load($ctx.Web)
  74: $ctx.Load($ctx.Site)
  75: $ctx.Load($ctx.Site.RootWeb)
  76: $ctx.ExecuteQuery()
  77:  
  78: $taxSession =
  79: [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($ctx)
  80:  
  81: Inherit-Navigation-For-Web $ctx $ctx.Web $taxSession
Script recursively goes through all sub sites in Sharepoint Online site collection and inherits their navigation settings from parent via Microsoft.SharePoint.Client.Publishing.Navigation.WebNavigationSettings class.

Friday, December 16, 2016

Sharepoint Online slowness when site collection contains many sub sites

Recently on several Sharepoint Online sites which belong to different tenants we encountered with similar problem with slowness. Slowness didn’t appear immediately, i.e. from beginning sites worked fast. But after some time response time grew to 6-14 seconds, which made sites almost unusable. First of all we checked SPRequestDuration response header in browser developer tools which shows server response time (see Diagnosing performance issues with SharePoint Online). It showed that slowness is not caused by client side custom components but instead comes from server side because SPRequestDuration had value from 6000 to 14000 milliseconds. All sub sites used OTB seattle.master master page without customizations. After deeper investigation we found that the problem is caused by structural navigation used for left navigation (for top navigation managed metadata navigation was used). I.e. with time number of sub sites in hierarchy grew up and it made structural left navigation work much slower.

We opened ticket in MS for that, but as workaround for this issue you may change left navigation from structural to managed metadata. After we did it on all sub sites (as in Sharepoint single term set with navigation terms can be used on single site, we set managed metadata left navigation on the root site and then inherited left navigation from the parent sites on all sub sites. This approach also has own problems, because in OTB master page only 2 levels of hierarchy are shown. Better approach will is to create own navigation term set for each sub site, but there you also need to think how to keep navigation terms synced with site structure), server response time was reduced to 1 second. In one of the future posts I will describe how you may keep in sync sites structure and managed metadata navigation terms hierarchy.

Wednesday, December 7, 2016

Calls to Microsoft Graph client library hangs in ASP.Net web forms web application

Recently I faced with strange problem: when tried to use Microsoft Graph client library in ASP.Net web forms web application (tried to run it in Azure web site) code hang and Request timeout exception was thrown after request processing time reached timeout value. E.g. if we will use example which I showed in one of the previous posts (see Work with Azure AD via Microsoft Graph API):

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         enumGroups();
   6:         Console.ReadKey();
   7:     }
   8:  
   9:     static async void enumGroups()
  10:     {
  11:         var graph = new GraphServiceClient(new AzureAuthenticationProvider());
  12:         try
  13:         {
  14:             var groups = await graph.Groups.Request().GetAsync();
  15:             foreach (var g in groups)
  16:             {
  17:                 Console.WriteLine(g.DisplayName);
  18:             }
  19:         }
  20:          catch (ServiceException x)
  21:         {
  22:             Console.WriteLine("Exception occured: {0}", x.Error);
  23:         }
  24:     }
  25: }

thread hang on line 16 when we perform actual call to graph API for retrieving groups:

   1: var groups = await graph.Groups.Request().GetAsync();

However the same code worked well in console application even if we would remove async/await from enumGroups method and would call client library synchronously. In ASP.Net web forms it is little bit more tricky. In order to make it work you need to make all methods up in the call stack asynchronous including page itself (see Using Asynchronous Methods in ASP.NET 4.5). I.e. in .aspx file add Async=”true” attribute to the Page directive:

   1: <%@ Page Async="true" ...

After that rewrite page method which calls Graph client library in async way:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     RegisterAsyncTask(new PageAsyncTask(Page_LoadAsync));
   4: }
   5:  
   6: private async Task Page_LoadAsync()
   7: {
   8:     await enumGroups();
   9: }

(if you have other methods in call stack between Page_LoadAsync and enumGroups you need to make them async as well). After that call to Microsoft Graph client library shouldn’t stuck anymore and should work as expected. There is also similar issue with ASP.Net MVC web applications: Application hang when executing AuthenticationContext.AcquireTokenAsync. Solution is also similar: you have to change MVC actions from sync to async. Exactly this article pointed me to the right direction for ASP.Net web forms, so big thanks to the author.

Saturday, December 3, 2016

Problem with missing users in group membership page in Sharepoint

Some time ago we faced with strange problem: on one of Sharepoint 2013 sites some users were not shown in their groups’ membership page (Site settings > Users and groups: http://example.com/_layouts/15/people.aspx?MembershipGroupId=groupId). Such users were able to login to Sharepoint and had all necessary permissions, but they didn’t appear on this page. Also need to notice that this site was migrated from SP2007. When I checked groups of the missing user via PowerShell:

   1: $u = Get-SPUser -Id "loginName" -Web http://example.com
   2: $u.Groups

they were properly returned. Also it worked as expected when I checked it in opposite way: check members of the group:

   1: $web = Get-SPWeb http://example.com
   2: $g = $web.SiteGroups["Foo"]
   3: $g.Users

User was shown there as well. And when I checked permissions via the following useful script which shows both permissions granted explicitly to the user and permissions granted via groups:

   1: $url = "http://example.com"
   2: Get-SPUser -Web $url -Limit All | sort-object UserLogin | select UserLogin,
   3:     @{name=”Exlicit given roles”;expression={$_.Roles}},
   4:     @{name=”Roles given via groups”;expression={$_.Groups | %{$_.Roles}}},
   5:     Groups | format-Table -auto

It showed that user has necessary permissions. I.e. the problem was only that user was not shown on the group membership page. In order to fix the issue the following script was used:

   1: $web = Get-SPWeb http://example.com
   2: $g = $web.Groups["Foo"]
   3: $users = $group.Users
   4: foreach ($u in $users)
   5: {
   6:     Write-Host "Fix user" $u.LoginName
   7:     $g.RemoveUser($u)
   8:     $g.AddUser($u)
   9:     $g.Update()
  10: }

It goes through all members of specified groups and then re-add them to this group. After that user appeared on group membership page.