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.

2 comments:

  1. Hi Alexey
    Why don't you use PowerShell instead? I think the PnP code make sense to e.g. use it in even Receiver and apply a predefined look to newly created sites, but to change it on 4000 existing sites? I wouldn't...
    YOu are much better off writing your own PowerShell Script

    ReplyDelete
  2. Hi Kinga,
    yes, at the end we went with PowerShell script approach

    ReplyDelete