Thursday, January 18, 2018

Enable possibility to download json files in Sharepoint

Sometime you may need to enable possibility to download json file types in Sharepoint (e.g. we had a case when it was needed for integration purposes – lobby screen at building reception used json file from Sharepoint site to show data to visitors). By default if you will put this file e.g. into /_layouts folder and try to access json file in context of Sharepoint site:

http://example.com/_layouts/test/foo.json

you will get the following error:

The page must have a <%@ webservice class="MyNamespace.MyClass" ... %> directive.
 
Stack trace:    at System.Web.UI.SimpleWebHandlerParser.ParseReader
()
   at System.Web.UI.SimpleWebHandlerParser.Parse(ICollection
referencedAssemblies)
   at System.Web.Compilation.SimpleHandlerBuildProvider.get_CodeCompilerTy
pe()
   at System.Web.Compilation.BuildProvider.GetCompilerTypeFromBuildProvider
(BuildProvider buildProvider)
   at System.Web.Compilation.BuildProvidersCompiler.ProcessBuildProviders()
   at System.Web.Compilation.BuildProvidersCompiler.PerformBuild()
   at System.Web.Compilation.BuildManager.CompileWebFile(VirtualPath
virtualPath)
   at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal
(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean
allowBuildInPrecompile)
   at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert
(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean
allowCrossApp, Boolean allowBuildInPrecompile)
   at System.Web.UI.WebServiceParser.GetCompiledType(String inputFile,
HttpContext context)
   at System.Web.Services.Protocols.WebServiceHandlerFactory.GetHandler
(HttpContext context, String verb, String url, String filePath)
   at System.Web.Script.Services.ScriptHandlerFactory.GetHandler
(HttpContext context, String requestType, String url, String pathTranslated)
   at System.Web.HttpApplication.MaterializeHandlerExecutionStep.System.Web
.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step,
Boolean& completedSynchronously)

In order to enable possibility to download json file from Sharepoint we need to do the following:

1. Create new application sub folder under your Sharepoint site in IIS manager. If you already have virtual folder you need to convert it to application also in IIS manager. Separate folder is needed in order to not affect whole Sharepoint, but only one exact url:

http://example.com/testappfolder

2. Add web.config to this folder and add mime type record for json extension under system.webServer > staticContent:

   1: <configuration>
   2:    <system.webServer>
   3:       <staticContent>
   4:          <mimeMap fileExtension=".json" mimeType="application/json" />
   5:       </staticContent>
   6:    </system.webServer>
   7: </configuration>

3. In IIS manager open handler mappings for your application sub folder and remove handler for *.json (that's why it is important to use separate application sub folder in IIS – in case of app this change will only affect this sub folder, not whole Sharepoint site. I.e. there won’t be side effects with this approach).

Now if we try to access http://example.com/testappfolder/foo.json its content will be successfully downloaded.

Thursday, December 14, 2017

Problem with terminated site workflows which continue working in Sharepoint Online

Recently we faced with interesting issue in Sharepoint Online: there were several 2013 site workflows on different sub sites which worked this way:

  1. Iterate through publishing pages in Pages doclib of the current site and if current date is close to page’s Valid to date, workflow sends reminder email to responsible person
  2. Wait 1 day and then repeat iteration through pages

i.e. by itself workflow never ends. However it is possible to terminate workflow from UI: Site contents > Site workflows > click workflow > End workflow (see my previous post Restart site workflows in Sharepoint Online)

We published new version from Sharepoint Designer, ended previous workflow instances from UI and started new instances. And here we found interesting issue: terminated workflow instances continued to work, i.e. they continued to send emails of previous version to the users. In UI their status was displayed as Terminated. At the moment we contacted MS support but if you know about this issue please share it in comments.

Monday, December 11, 2017

Install SPFx React Script editor web part as tenant-scoped solution into Sharepoint Online

How it is said in the following MS article Tenant-Scoped solution deployment for SharePoint Framework solutions, it is possible to make SPFx solution tenant scoped. According to documentation components installed with such solution will become immediately available cross the tenant after solution package will be installed to tenant app catalog. Let’s check few internal details of tenant-scoped solutions using Script editor web part for modern pages built in React as example.

Before to start we will need to install the following prerequisites:

  • git for Windows
  • nodejs for Windows
  • also I usually need to globally install gulp
    npm install -g gulp
    and add resolved folder %appdata%\npm to PATH environment variable

So first of all we need to clone git repository with web part code:

git clone https://github.com/SharePoint/sp-dev-fx-webparts.git

After that go to ~/sp-dev-fx-webparts\samples\react-script-editor and run the following commands:

npm install

which will install necessary dependencies into node_modules subfolder (there are more that 1600 dependencies so it will take some time). After that edit config/package-solution.json file: add "skipFeatureDeployment": true there:

it will make our solution tenant-scoped. Also edit config\write-manifest.json file: set cdnBasePath variable to your CDN path, e.g.

https:<tenant>.sharepoint.com/sites/CDN/SiteAssets/SPFx/react-script-editor

After that run the following commands:

gulp --ship

gulp package-solution --ship

First one should create temp folder. You need go to its deploy subfolder and copy files from there to specified CDN. Second command will create sharepoint/solution folder with pzl-script-editor.sppkg file. This file is zip file so if we will unzip it and check AppManifest.xml we will find that it has SkipFeatureDeployment=”true” attribute:

Now deploy to App catalog and enable "Make this solution available to all sites in the organization" option:

After that solution should be available on all sites of your tenant.

Thursday, December 7, 2017

Restart site workflows in Sharepoint Online

Once you have published or made changes in existing site workflow in Sharepoint Online site you will need to restart existing running workflow instances in order to get new changes into use (otherwise they won’t take effect). In order to restart existing site workflow instance in Sharepoint Online you may use the following steps. Go to Site settings > Site contents and click “Site workflows” link on the top of the page:

Opened page will show list of all available site workflows together with list of running workflows and list of completed workflows:

If you will click on some workflow name from the top list – it will be started. If you will click on some running workflow instance workflow details page will be opened:

On this page there is a link “End workflow” (it looks like regular text, not as a link). By clicking this link you may stop existing workflow instance, then return to previous page and start new instance which will use new published workflow version.

Thursday, November 30, 2017

AnonymousPermMask64 for different anonymous settings for Sharepoint lists and document libraries

In addition to changing anonymous settings for the Sharepoint sites (see AllowAnonymousAccess, AnonymousState and AnonymousPermMask64 properties for Sharepoint sites with different anonymous configurations) it may be needed to enable anonymous access for particular lists and doclibs. In this case you need to set Anonymous users can access: Lists and libraries for the parent web (see above link) and enable anonymous access for the list/doclib. It is done from List settings > Permissions for this document library > Anonymous Access. Here you may check only View items permissions for anonymous users on regular web sites with read anonymous access. Let’s see how SPList.AnonymousPermMask64 property will be changed.

1. No anonymous access

In this case SPList.AnonymousPermMask64 = EmptyMask

2. View Items

Now SPList.AnonymousPermMask64 = ViewListItems, OpenItems, ViewVersions, ViewFormPages, Open, UseClientIntegration.

AllowAnonymousAccess, AnonymousState and AnonymousPermMask64 properties for Sharepoint sites with different anonymous configurations

In Sharepoint it is possible to use several anonymous configurations for the sites. They may be changed from Site settings > Site permissions > Anonymous access:

  • Entire Web site
  • Lists and libraries
  • Nothing

Here what description says about them:

Specify what parts of your Web site (if any) anonymous users can access. If you select Entire Web site, anonymous users will be able to view all pages in your Web site and view all lists and items which inherit permissions from the Web site. If you select Lists and libraries, anonymous users will be able to view and change items only for those lists and libraries that have enabled permissions for anonymous users.

And Nothing means that site doesn’t have anonymous access. Depending on used setting Sharepoint changes 3 properties of corresponding SPWeb object: AllowAnonymousAccess, AnonymousState and AnonymousPermMask64. Let’s see how they are changed depending on selection:

1. Anonymous users can access: Entire Web site

Property Value
AllowAnonymousAccess True
AnonymousState On
AnonymousPermMask64 ViewListItems, ViewVersions, ViewFormPages, Open, ViewPages, UseClientIntegration

2. Anonymous users can access: Lists and libraries

Property Value
AllowAnonymousAccess False
AnonymousState Enabled
AnonymousPermMask64 Open

2. Anonymous users can access: Nothing

Property Value
AllowAnonymousAccess False
AnonymousState Disabled
AnonymousPermMask64 EmptyMask

Monday, November 27, 2017

Problem with slow setting of composed look for Sharepoint site via OfficeDevPnP SetComposedLookByUrl method

In order to set composed look for Sharepoint site we may use SetComposedLookByUrl extension method from OfficeDevPnP library. According to documentation it does the following:

Retrieves the named composed look, overrides with specified palette, font, background and master page, and then recursively sets the specified values.

Here is the code of this method:

   1: public static void SetComposedLookByUrl(this Web web, string lookName,
   2:     string paletteServerRelativeUrl = null, string fontServerRelativeUrl = null,
   3:     string backgroundServerRelativeUrl = null, string masterServerRelativeUrl = null,
   4:     bool resetSubsitesToInherit = false, bool updateRootOnly = true)
   5: {
   6:     var paletteUrl = default(string);
   7:     var fontUrl = default(string);
   8:     var backgroundUrl = default(string);
   9:     var masterUrl = default(string);
  10:  
  11:     if (!string.IsNullOrWhiteSpace(lookName))
  12:     {
  13:         var composedLooksList = web.GetCatalog((int)ListTemplateType.DesignCatalog);
  14:  
  15:         // Check for existing, by name
  16:         CamlQuery query = new CamlQuery();
  17:         query.ViewXml = string.Format(CAML_QUERY_FIND_BY_FILENAME, lookName);
  18:         var existingCollection = composedLooksList.GetItems(query);
  19:         web.Context.Load(existingCollection);
  20:         web.Context.ExecuteQueryRetry();
  21:         var item = existingCollection.FirstOrDefault();
  22:  
  23:         if (item != null)
  24:         {
  25:             var lookPaletteUrl = item["ThemeUrl"] as FieldUrlValue;
  26:             if (lookPaletteUrl != null)
  27:             {
  28:                 paletteUrl = new Uri(lookPaletteUrl.Url).AbsolutePath;
  29:             }
  30:             var lookFontUrl = item["FontSchemeUrl"] as FieldUrlValue;
  31:             if (lookFontUrl != null)
  32:             {
  33:                 fontUrl = new Uri(lookFontUrl.Url).AbsolutePath;
  34:             }
  35:             var lookBackgroundUrl = item["ImageUrl"] as FieldUrlValue;
  36:             if (lookBackgroundUrl != null)
  37:             {
  38:                 backgroundUrl = new Uri(lookBackgroundUrl.Url).AbsolutePath;
  39:             }
  40:             var lookMasterUrl = item["MasterPageUrl"] as FieldUrlValue;
  41:             if (lookMasterUrl != null)
  42:             {
  43:                 masterUrl = new Uri(lookMasterUrl.Url).AbsolutePath;
  44:             }
  45:         }
  46:         else
  47:         {
  48:             Log.Error(Constants.LOGGING_SOURCE,
  49: CoreResources.BrandingExtension_ComposedLookMissing, lookName);
  50:             throw new Exception($"Composed look '{lookName}' can not be found; pass " +
  51: "null or empty to set look directly (not based on an existing entry)");
  52:         }
  53:     }
  54:  
  55:     if (!string.IsNullOrEmpty(paletteServerRelativeUrl))
  56:     {
  57:         paletteUrl = paletteServerRelativeUrl;
  58:     }
  59:     if (!string.IsNullOrEmpty(fontServerRelativeUrl))
  60:     {
  61:         fontUrl = fontServerRelativeUrl;
  62:     }
  63:     if (!string.IsNullOrEmpty(backgroundServerRelativeUrl))
  64:     {
  65:         backgroundUrl = backgroundServerRelativeUrl;
  66:     }
  67:     if (!string.IsNullOrEmpty(masterServerRelativeUrl))
  68:     {
  69:         masterUrl = masterServerRelativeUrl;
  70:     }
  71:  
  72:     //URL decode retrieved url's
  73:     paletteUrl = System.Net.WebUtility.UrlDecode(paletteUrl);
  74:     fontUrl = System.Net.WebUtility.UrlDecode(fontUrl);
  75:     backgroundUrl = System.Net.WebUtility.UrlDecode(backgroundUrl);
  76:     masterUrl = System.Net.WebUtility.UrlDecode(masterUrl);
  77:  
  78:     web.SetMasterPageByUrl(masterUrl, resetSubsitesToInherit, updateRootOnly);
  79:     web.SetCustomMasterPageByUrl(masterUrl, resetSubsitesToInherit, updateRootOnly);
  80:     web.SetThemeByUrl(paletteUrl, fontUrl, backgroundUrl, resetSubsitesToInherit,
  81:         updateRootOnly);
  82:  
  83:     // Update/create the "Current" reference in the composed looks gallery
  84:     string currentLookName = GetLocalizedCurrentValue(web);
  85:     web.CreateComposedLookByUrl(currentLookName, paletteUrl, fontUrl, backgroundUrl,
  86:         masterUrl, displayOrder: 0);
  87: }

The problem is that on the real production environments this method may work very slow (we needed to set composed look on all sub sites, i.e. pass resetSubsitesToInherit = true and updateRootOnly = false). When apply it on the site with many sub sites (about 4000 sub site) it worked almost 15 hours and then was interrupted with network connectivity exception.

Another problematic moment is that method doesn’t show any progress indicator, i.e. you just have to wait watching black screen all that time. At the moment I see only 1 workaround which at least will allow to see progress of the work: iterate through sub sites in own external loop, print url of currently updated web and call SetComposedLookByUrl for each web site separately with resetSubsitesToInherit = false and updateRootOnly = true. If you know other solutions please share them in comments.