SharePoint 2010 : Développement SharePoint Online, Personnalisation Graphique et Packaging – Partie 2

Ce billet fait partie de la série de posts liés au développement sur SharePoint Online. Il fait suite au post précédent qui à pour but de montrer qu’on peut configurer automatiquement un site SharePoint hebergé dans le cloud de Microsoft en un seul clic.

Le but étant de partir du fameux site SharePoint classique pour arriver à un site complètement personnalisé en un clic de feature.

image

Feature Désactivée

Feature Activée

image image

Dans le précédent post, on a vu comment uploader les fichiers nécessaires à ce changement de look & feel et à les publier proprement (archiver, approuver et publier pour être précis) dans une solution Sandboxed exécutée sur un SharePoint Online.

Le but est maintenant de voir comment :

  • Lier les MasterPages aux sites et assigner le nouveau layout à la page par défaut
  • Insérer du contenu HTML dans la publishing page.
  • Changer les valeurs de configuration de la recherche

Lier les Masterpages et autres dépendances graphiques

// Enumerate through each site and apply branding.
foreach (SPWeb web in siteCollection.AllWebs)
{
    try
    {
        //Only if Search or Publishing webtemplate
        if (web.WebTemplateId == 90 || web.WebTemplateId == 53)
        {
            if (web.WebTemplateId == 90)
                web.CustomMasterUrl = webAppRelativePath + "_catalogs/masterpage/Synesthesia-search.master";
            else
            {
                web.CustomMasterUrl = webAppRelativePath + "_catalogs/masterpage/Synesthesia-home.master";
                web.MasterUrl = webAppRelativePath + "_catalogs/masterpage/Synesthesia.master";
                web.CustomJavaScriptFileUrl = webAppRelativePath + "Style Library/Synesthesia/jquery.nivo.slider.js";
  }
 
            web.UIVersion = 4;
            web.Update();
        }
    }
    // Best Practices: Using Disposable Windows SharePoint Services Objects
    // http://msdn.microsoft.com/en-us/library/aa973248.aspx 
    finally
    {
        if (web != null)
            web.Dispose();
    }
}

Dans ce bout de code (dans le lequel, je ne catche aucune exception…), j’itère sur les sites de ma collection et effectue le changement de mes masterpages uniquement sur les Search ou Publishing Webtemplate.

Il faut simplement savoir que j’utilise une masterpage sensiblement différente pour le site de recherche et que l’utilisation de CustomMasterUrl et MasterUrl dépend de la règle suivante :

  • Les pages de publications utilisent le token ~masterurl/custom.master
  • Toutes les autres pages standards (formulaires et view pages) utilisent le token ~masterurl.default.master

Ainsi, en utilisant CustomMasterUrl & MasterUrl, je change les valeurs de ces token dynamiquement et du coup les pages chargent les nouvelles masterpages.

Bien sur, on n’oublie pas de respecter les bonnes pratiques de développement et on pense à libérer explicitement le SPWeb

Nota Bene :

Si ce que je viens de vous dire est du chinois pour vous et que la page Best Practices: Using Disposable Windows SharePoint Services Objects (http://msdn.microsoft.com/en-us/library/aa973248.aspx) vous semble difficile à digérer, je vous conseille d’utiliser SPDisposeCheck http://archive.msdn.microsoft.com/SPDisposeCheck. SPDisposeCheck est un outil qui aide les développeurs et les administrateurs à vérifier que les développements SharePoint respecttent les bonnes pratiques en terme de libération des ressources.

C’est pratique, rapide et ca peut éviter que votre administrateur vous séquestre pendant 3 semaines parce que vos fuites mémoire ont fait crasher sa ferme SharePoint…

Assigner un nouveau modèle de page à la page d’acceuil

   1:  else
   2:  {
   3:      web.CustomMasterUrl = webAppRelativePath + "_catalogs/masterpage/Synesthesia-home.master";
   4:      web.MasterUrl = webAppRelativePath + "_catalogs/masterpage/Synesthesia.master";
   5:      web.CustomJavaScriptFileUrl = webAppRelativePath + "Style Library/Synesthesia/jquery.nivo.slider.js";
   6:   
   7:      // Unfortunately there is no EASY way to change a Pagelayout's Publishing page from a Sandbox Solution, 
   8:      // So it's quick & dirty mode ON. Thx God for Reflector.
   9:      SPFile defaultPage = web.GetFile(web.ServerRelativeUrl + "pages/default.aspx");
  10:      string layoutUrl = webAppRelativePath + "_catalogs/masterpage/synesthesia.aspx";
  11:      string layoutDescription = "Synesthesia Page Layout";
  12:      SetPageLayout(defaultPage, layoutUrl, layoutDescription);
  13:   
  14:      string htmlContent = @"<br/><br/><br/><br/><div class='col'><h3>Quisque Vel Lorem</h3><div class='ft'><p>Quisque vel lorem eu libero laoreet facilisis. Aenean placerat, ligula quis placerat iaculis, mi magna luctus nibh, adipiscing pretium erat neque vitae augue.</p><p><a href='#'>Read More</a></p></div></div><div class='col'><h3>Placerat Iaculis</h3><div class='ft'><p>Quisque vel lorem eu libero laoreet facilisis. Aenean placerat, ligula quis placerat iaculis, mi magna luctus nibh, adipiscing pretium erat neque vitae augue.</p><p><a href='#'>Read More</a></p></div></div><div class='col last'><h3>Vitae Augue</h3><div class='ft'><p>Quisque vel lorem eu libero laoreet facilisis. Aenean placerat, ligula quis placerat iaculis, mi magna luctus nibh, adipiscing pretium erat neque vitae augue.</p><p><a href='#'>Read More</a></p></div></div>";
  15:      ReplacePublishingPageContent(defaultPage, htmlContent);
  16:  }

Ici, on a un petit problème… Dans un monde normal (comprenez dans une Solution de type Ferme), j’aurais accès au modèle objet complet de SharePoint et je pourrais faire ce genre de chose :

  • Récupèrer mon PublishingWeb
    • PublishingWeb pubWeb = PublishingWeb.GetPublishingWeb(web))
  • Récupèrer mon PageLayout
    • soit via pubWeb.GetAvailablePageLayouts() soit via une requête CAML
  • Récupèrer ma page
    • PublishingPage.GetPublishingPage(listItem)
  • Assigner mon Layout
    • page.Layout = newLayout
  • Mettre à jour.

Sauf que dans un univers Sandboxed & (et en plus dans SharePoint Online), je n’ai pas accès au namespace Publishing. Donc il va falloir ruser :

Il faut savoir que dans SharePoint, tout objet hérite potentiellement de SPListItem et le reste n’est que Métadonnées supplémentaires. L’utilisation du namespace Publishing ne fait rien de plus que de rajouter une couche d’abstraction pour nous simplifier la vie au jour le jour.

Mais rien ne nous empêche de le faire à l’ancienne en prenant bien soin de lancer reflector avant et de voir ce qu’il y a dans la DLL en question :

Au final, vous l’aurez compris, la partie intéressante est dans la fonction SetPageLayout(defaultPage, layoutUrl, layoutDescription);

   1:  /// <summary>
   2:  /// Sets the page layout.
   3:  /// </summary>
   4:  /// <param name="page">The page.</param>
   5:  /// <param name="layoutUrl">The layout URL.</param>
   6:  /// <param name="layoutDescription">The layout description.</param>
   7:  private void SetPageLayout(SPFile page, string layoutUrl, string layoutDescription)
   8:  {
   9:      EnableChanges(page);
  10:   
  11:      SPListItem pageItem = page.Item;
  12:   
  13:      Guid pageLayoutFieldGuid = new Guid("0f800910-b30d-4c8f-b011-8189b2297094");
  14:   
  15:      pageItem[pageLayoutFieldGuid] = new SPFieldUrlValue()
  16:      {
  17:          Url = layoutUrl,
  18:          Description = layoutDescription
  19:      };
  20:      pageItem.Properties["PublishingPageLayoutName"] = layoutDescription;
  21:      pageItem.Update();
  22:      page.Update();
  23:   
  24:      FinalizeChanges(page);
  25:  }
  26:   
  27:  /// <summary>
  28:  /// Finalizes the changes.
  29:  /// </summary>
  30:  /// <param name="file">The file.</param>
  31:  private void FinalizeChanges(SPFile file)
  32:  {
  33:      // Depending on the settings of the parent document library we may need to check in and/or (publish or approve) the file
  34:      if (file.Level == SPFileLevel.Checkout)
  35:          file.CheckIn(string.Empty, SPCheckinType.MajorCheckIn);
  36:      if (file.Level == SPFileLevel.Draft)
  37:      {
  38:          if (file.DocumentLibrary.EnableModeration)
  39:              file.Approve(string.Empty);
  40:          else
  41:              file.Publish(string.Empty);
  42:      }
  43:  }
  44:   
  45:  /// <summary>
  46:  /// Enables the changes.
  47:  /// </summary>
  48:  /// <param name="file">The file.</param>
  49:  private void EnableChanges(SPFile file)
  50:  {
  51:      if (file.RequiresCheckout && file.CheckOutType == SPFile.SPCheckOutType.None)
  52:      {
  53:          file.CheckOut();
  54:      }
  55:  }

Le bout de code qui nous intéresse ici est de la ligne 5 à 16 :

   1:  private void SetPageLayout(SPFile page, string layoutUrl, string layoutDescription)
   2:  {
   3:      …;
   4:   
   5:      SPListItem pageItem = page.Item;
   6:   
   7:      Guid pageLayoutFieldGuid = new Guid("0f800910-b30d-4c8f-b011-8189b2297094");
   8:   
   9:      pageItem[pageLayoutFieldGuid] = new SPFieldUrlValue()
  10:      {
  11:          Url = layoutUrl,
  12:          Description = layoutDescription
  13:      };
  14:      pageItem.Properties["PublishingPageLayoutName"] = layoutDescription;
  15:      pageItem.Update();
  16:      page.Update();
  17:   
  18:      …;
  19:  }

Cette portion de code est plus ou moins directement importée du code que j’ai pu lire dans Reflector en regardant ce qu’il se passe quand on fait Page.Layout = newLayout

Au final, je récupère l’élément correspondant à la page, stocke l’url du nouveau modèle de page dans la bonne métadonnée, je rajoute la description dans le properties bag de l’élément et j’update le tout

Au final, le code est simplissime mais la façon de faire est un peu différente de ce qu’on est habitué à faire en général.

Insérer du contenu HTML dans la page d’accueil

…Qui est une page de publication donc à priori on va avoir le même problème… et donc la même solution !

/// <summary>
/// Replaces the content of the publishing page.
/// </summary>
/// <param name="page">The page.</param>
/// <param name="htmlContent">Content of the HTML.</param>
private void ReplacePublishingPageContent(SPFile page, object htmlContent)
{
    EnableChanges(page);
 
    SPListItem pageItem = page.Item;
 
    // Can't Activate on SPO cause of FieldId, Publishing namespace.
    //pageItem[FieldId.PublishingPageContent] = htmlContent;
 
    Guid publishingPageContentFieldGuid = new Guid("F55C4D88-1F2E-4ad9-AAA8-819AF4EE7EE8");
    pageItem[publishingPageContentFieldGuid] = htmlContent;
    pageItem.Update();
    page.Update();
 
    FinalizeChanges(page);
}

On remarquera ici, une petite différence.

En effet, dans une sandbox développée sur mon environnement de développement le code pageItem[FieldId.PublishingPageContent] = htmlContent fonctionnait parfaitement.

Lors de mon passage sur l’environnement SharePoint Online, il m’a été impossible de déployer ma sandboxed solution parce que je faisais référence à FieldID. J’ai du récupèrer la valeur du champ PublishingPageContent et le spécifier en dur dans mon code.

Rien de bien grave mais c’est à noter qu’il peut y avoir des différences entre votre environnement Sandboxed et celui de SPO.

Changer les valeurs de configuration de la recherche

Et pour finir sur une petite pointe d’humour, je vais vous montrer comment arriver à changer les configurations liées à la recherche dans la collection de site :

La première chose qui surprend, c’est que vous n’avez aucune propriété à ce sujet sur votre SPSite… Du coup, mon premier réflexe a été de regarder le nom de la page applicative qui me permettait de faire ce changement dans SharePoint.

Ce qui m’a forcément ramener à Reflector et à examiner exactement ce qui se passait dans ce bout de code. Et bien figurez vous que les informations sont stockées à cet endroit :

  • rootWeb.AllProperties["SRCH_ENH_FTR_URL"]
  • rootWeb.AllProperties["SRCH_TRAGET_RESULTS_PAGE"]

Non, je n’ai pas fait de typos dans la chaine de caractères des propriétés... Du coup, j’applique le même code dans ma sandbox :

// Doesn't work (ie. DO NOTHING) when run inside a Sandbox Solution
// Cf : http://www.moss2007.be/blogs/vandest/archive/2011/03/06/sharepoint-spweb-properties-versus-spweb-allproperties.aspx
rootWeb.AllProperties["SRCH_ENH_FTR_URL"] = webAppRelativePath + "Search";
rootWeb.AllProperties["SRCH_TRAGET_RESULTS_PAGE"] = webAppRelativePath + "Search/Results.aspx";
rootWeb.Update();

Sauf que rien ne se passe… Aucune exceptions, messages d’erreur, rien dans les fichiers de log, rien... En mode debug, mon code est bien exécuté mais il ne change pas la valeur de la propriété.

Deuxième réflexe, un petit bing/google pour voir si je suis le seul à avoir fait ce genre de chose (la réponse est bien évidemment non), et je tombe sur un post de blog très intéressant http://www.moss2007.be/blogs/vandest/archive/2011/03/06/sharepoint-spweb-properties-versus-spweb-allproperties.aspx

En résumé :

  • Si la propriété SRCH_ENH_FTR_URL n’est pas entièrement en majuscules, ca ne marchera pas.
  • Si vous êtes dans un environnement Sandbox, SPWeb.AllProperties est uniquement accessible en ReadOnly. Mais si vous essayez de mettre à jour le table de Hash, aucune exception ne sera lancée, il ne se passera rien tout simplement.

Néanmoins, je reste persuadé qu’il est possible de changer cette valeur peut être pas par code mais de manière littérale (XML), peut être à l’instanciation du site… A voir.

Dans tout les cas, voilà qui clôt le sujet sur la personnalisation graphique, je pensais parler dans mes prochains billets des possibilités de SharePoint Online & Silverlight ou potentiellement d’une application complète de Covoiturage avec BingMaps.

Si vous avez d’autres idées, je serai ravi de les entendre.

<Phil/>

Publié lundi 6 juin 2011 10:00 par phil
Classé sous , , , , , , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

About phil

Philippe Sentenac est Consultant SharePoint à Wygwam en région Parisienne. Il intervient essentiellement sur des missions liées à SharePoint (2007 et 2010 ) mais aussi autour du Web 2.0. Plus généralement, il s'intéresse à l'ASP.Net (MVC) , à Silverlight, et à tout ce qui est orienté Web en rapport avec les nouvelles technologies, qu'il pratique depuis 2006. Féru de développement, il est passionné par les problématiques de méthodologies et d'industrialisation du développement.

Les 10 derniers blogs postés

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01

- SharePoint Online: Script PowerShell pour supprimer une colonne dans tous les sites d’une collection par Blog Technique de Romelard Fabrice le 11-27-2018, 18:01