SharePoint 2013

[SP2013-SP2016] – Utiliser un JSLink pour afficher un slider

Posted on Updated on

Bonjour à tous,

Cela fait un moment que je n’ai rien publié, mais j’ai été pas mal occupé 🙂

Aujourd’hui, comment créer un jslink qui affichera les images d’une bibliothèque d’image sous forme de Slideshow en JavaScript en utilisant les jslink.

1/ Créer une bibliothèque d’images et ajouter des images

Première étape donc, créer une bibliothèque d’image dans un site SharePoint. Facilement réalisable, à la souris via l’interface de création des applications. Pour cela, rendez-vous sur votre site SharePoint concerné, puis utilisez l’engrenage (Paramètres / Settings), puis créer une Application (Add an app) :

01

 

Puis sélectionner le type d’application “Bibliothèque d’images” (Picture Library) :

02

 

Ici je l’appellerai “Images” pour cet exemple :

03

 

L’application “Images” de type bibliothèque d’images est créée :

04

 

Ensuite, je vais charger un ensemble d’images que je souhaite afficher dans mon slideshow. Pour cela, j’utilise le site Pexels (https://www.pexels.com/) mais vous pourrez en trouver sur beaucoup de sites (Attention au droit à l’image) :

05

 

Puis je les télécharger dans SharePoint (via glisser-déposer, mais pour pouvez évidemment utiliser le bouton télécharger, Windows Explorer etc.) :

06

 

Mes images sont donc maintenant dans SharePoint.

 

2/ Les ressources nécessaires

Afin que le diaporama soit animé, et de ne pas redévelopper la roue, j’ai choisi d’utiliser un JavaScript bien connu afin de créer mon diaporama qui s’appelle slidejs (http://www.slidesjs.com/). Ce script est assez facile d’utilisation, il est responsive, “touchable” (tablettes, smartphones) et utilise CSS3 (attention aux navigateurs non-compatibles… ça existe encore).

Il peut également démarrer tout seul (auto-play), utilise des transitions etc. Tout ceci peut être personnalisé suivant les besoins. Ici, je voudrai qu’il change de photo seulement quand l’utilisateur clique sur le bouton suivant (ou précédent).

Je vais donc avoir besoin de charger JQuery, et jquery.slides.min.js.

Pour ma part, JQuery est chargé dans la masterpage de mon site (et le fichier est dans le hive car je l’utilise sur tous les sites : /template/layouts/demo/jquery-1.11.2.min.js) :

07

Et dans ma masterpage (je vous laisse voir les différents moyens de charger JQuery) :

<SharePoint:ScriptLink ID="ScriptLink6" language="javascript"  Name="/_layouts/15/demo/jquery-1.11.2.min.js" runat="server" Localizable="false"/>;

08

 

Pour la fichier jquery.slides.min.js, je vais le charger dans la bibliothèque de style de la collection de sites car il n’est utilisé que pour ce site, et pas sur les autres (rien ne vous empêche de le déployer dans le hive). Pour cela :

Accéder au contenu du site racine (qui est le site en question dans mon exemple), utilisez l’engrenage puis “Contenu du site” (Site Content) :

09

Puis accédez à l’application “Style Library” :

10

Dans cette application, je créé un nouveau répertoire pour isoler mes ressources, ce répertoire s’appellera “demo” :

11

12

13

Et enfin je télécharger le fichier “jquery.slides.min.js” dans ce nouveau répertoire :

14

 

Sans oublier de l’archiver puis de le publier :15

16

17

Et enfin, il nous faut télécharger dans ce même répertoire le fichier JSLink permettant de lancer le Slideshow. Nous allons voir maintenant ce qu’il contient…

3/ Le JSLink

Ici nous allons donc utiliser le fichier “SharePointSlideshow.js” (vous pourrez bien sûr le renommer).

Premièrement, nous allons déclarer un namespace spécifique :

Type.registerNamespace('DEMO');
DEMO.SPSlideshow = DEMO.SPSlideshow || {};
DEMO.SPSlideshow.Templates = DEMO.SPSlideshow.Templates || {}
DEMO.SPSlideshow.Functions = DEMO.SPSlideshow.Functions || {}

Puis déclarer un ID global pour le div qui encapsulera les éléments du slideshow :

var slideshowId = '';

Et enfin, déclarer les fonctions nécessaires :

Fonction affichant 1 élement :

DEMO.SPSlideshow.Functions.Display = function (ctxSlideshow) {

    var viewMoreLabel = "+ Read more";    
    var webpartTitle = "";

    /* DECLARE COLUMNS INTERNAL NAMES  */
    var ColumnIDInternalName = "ID";
    var ColumnTitleInternalName = "Title";
    var ColumnFileRefInternalName = "FileRef";   
    
    /* GET ITEM VALUES FROM CONTEXT  */
    var item = ctxSlideshow.CurrentItem;
    var itemId = item[ColumnIDInternalName];
    var itemTitle = item[ColumnTitleInternalName];
    var itemFileUrl = item[ColumnFileRefInternalName];
       
    //create img form jsslide library
    var strSlideshow = '<img src="' + itemFileUrl + '">';
    
    return strSlideshow;
}

Fonction générant le Header (div encapsulant la structure générale, voir documentation de slidejs) :

DEMO.SPSlideshow.Functions.GenerateHeader = function (ctxSlideshow) {

    var webpartId = ctxSlideshow.wpq;
    var webpartTitleCell = '#WebPartTitle' + webpartId;
    $("#WebPart" + webpartId).css("position","relative");
    var header = '<div class="container">';
    slideshowId = 'slides' + ctxSlideshow.ctxId;
    header += '<div id="' + slideshowId + '">';
    //insert buttons for next and previous
    header += '<a href="#" id="slideshowleftarrow" class="slidesjs-previous slidesjs-navigation"></a>';
    header += '<a href="#" id="slideshowrightarrow" class="slidesjs-next slidesjs-navigation"></a>';
    return header;
}

Fonction générant le Footer (div fermant la structure générale, voir documentation de slidejs):

DEMO.SPSlideshow.Functions.GenerateFooter = function (ctxSlideshow) {  
    var footer = '</div>';
    footer += '</div>';
    return footer;
}

Fonction permettant d’appliquer les éléments en JQuery sur la structure, et mettant en forme le slideshow :

DEMO.SPSlideshow.Functions.PostRender = function (ctxSlideshow) {   
    //start slide
    $(function () {
        var slidedivid = '#' + slideshowId;
        $(slidedivid).slidesjs({
            start: 2,
            pagination: {
                active: false
            }       
        });
    });    
}

Et enfin, le bout de script permettant de faire le “register” du JSLink sur le WebPart standard d’affichage des images (Application Picture Library) :

DEMO.SPSlideshow.Templates.RegisterSlideshowDisplay = function () {
// Fallback to loading jQuery from a CDN path if the local is unavailable
(window.jQuery || document.write('&lt;script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.11.2.min.js"&gt;&lt;\/script&gt;'));

var slideshowcontext = {};
slideshowcontext.Templates = {};
slideshowcontext.Templates.Header = DEMO.SPSlideshow.Functions.GenerateHeader;
slideshowcontext.Templates.Item = DEMO.SPSlideshow.Functions.Display;
slideshowcontext.Templates.Footer = DEMO.SPSlideshow.Functions.GenerateFooter;
slideshowcontext.Templates.OnPostRender = DEMO.SPSlideshow.Functions.PostRender;

slideshowcontext.ListTemplateType = 109;
slideshowcontext.BaseViewID = 6;

SPClientTemplates.TemplateManager.RegisterTemplateOverrides(slideshowcontext);
}

//Register CSR-MDS module then call template registration
DEMO.SPSlideshow.Functions.RegisterInMDS = function () {
//RegisterLikes-override for MDS disabled site (because we need to call the entry point function in this case whereas it is not needed for anonymous functions)
ExecuteOrDelayUntilScriptLoaded(DEMO.SPSlideshow.Templates.RegisterSlideshowDisplay, 'clienttemplates.js');
// RegisterLikes-override for MDS enabled site
RegisterModuleInit(_spPageContextInfo.siteServerRelativeUrl + "/Style Library/demo/SharePointSlideshow.js", DEMO.SPSlideshow.Templates.RegisterSlideshowDisplay);
}

//Load CSR-MDS module if feature is activated
if (typeof _spPageContextInfo != "undefined" &amp;&amp; _spPageContextInfo != null) {
DEMO.SPSlideshow.Functions.RegisterInMDS();
}
else {
ExecuteOrDelayUntilScriptLoaded(DEMO.SPSlideshow.Templates.RegisterSlideshowDisplay, 'clienttemplates.js');
}

Lorsque ce fichier est globalement (et syntaxiquement ok), il faut le télécharger avec le fichier précédent dans Style Library/Demo (on l’archive et publie également) :

18

 

4/ Utilisation

Pour utiliser ce JSLink, il faut tout d’abord ajouter le composant WebPart de l’application Picture Library sur une page. La page d’accueil par exemple. On édite la page en cliquant sur le bouton Modifier (Edit) dans le coin haut-droit :

19

Dans la galerie de WebParts, on choisit le WebPart associé à la nouvelle application Bibliothèque d’images (qui s’appelait “Images” dans mon cas) :

20

 

Et on l’ajoute dans la page, puis on sauvegarde la page :21

Ensuite, on repasse en édition sur la page, et on modifie les propriétés du composant WebPart que l’on vient d’ajouter :

19

Dans le coin en haut à droite du WebPart, on accède au menu “Modifier le composant WebPart” (Edit Web Part) :

22

 

La “boite à outils” du WebPart s’ouvre sur la droite :

23

Dans le dernier champ “JS Link”, il faut indiquer le chemin (relatif à la collection de sites) vers le fichier “SharePointSlideshow.js”. Dans mon cas, j’indique :

~sitecollection/Style%20Library/demo/jquery.slides.min.js|~sitecollection/Style%20Library/demo/SharePointSlideshow.js

J’utilise le token “~sitecollection” afin d’indiquer que mon url est relative à la collection de site (vous pouvez également utiliser ~site), SharePoint se chargera de recomposer l’url absolue. Et ensuite, j’utilise “|” afin de dire à SharePoint qu’il doit charger 2 scripts : “jquery.slides.min.js” et “SharePointSlideshow.js” :

24

 

On clique sur “OK”, on enregistre et immédiatement, le slideshow est fonctionnel (moche mais fonctionnel). En effet les boutons “Previous” et “Next” fonctionnement :

25 26

 

5/ Faire du beau…

En effet, le rendu est un peu sommaire… des liens cliquables, en bas… pas super ergonomique et même moche. Allez on fait du “un peu plus beau”. Et donc de la CSS !

Premièrement, je vais utiliser 4 images pour remplacer les liens Previous / Next par des flèches qui changeront de couleur lorsque la souris les survolera :

rightarrow-active rightarrow-inactive leftarrow-active leftarrow-inactive

 

 

Je vais télécharger ces images dans l’application “Style Library, dans le même dossier que les fichiers *.js téléchargés précédemment :

27

 

Puis enfin je vais créer une nouvelle feuille de style qui contiendra quelques directives pour mettre en place tout ça qui s’appellera SharePointSlideshow.css. Je choisis également de la déployer dans l’application Style Library et de la référencer dans la masterpage :

28

Dans la masterpage :

<SharePoint:CssRegistration ID="DemoCSS" Name="<% $SPUrl:~sitecollection/Style Library/demo/SharePointSlideshow.css %>" After="corev15.css" runat="server">

29

 

Et enfin le contenu de la CSS :

/* WebPart slideshow */

#slideshowleftarrow{
background:url('/sites/tests/Style%20Library/demo/leftarrow-inactive.png');
background-repeat:no-repeat;
height: 46px;
width: 35px;
position:absolute;
top:50%;
left:0;
z-index:98;

}

#slideshowrightarrow{
background:url('/sites/tests/Style%20Library/demo/rightarrow-inactive.png');
background-repeat:no-repeat;
height: 46px;
width: 35px;
position:absolute;
top:50%;
right:0;
z-index:98;

}

#slideshowleftarrow:hover{
background:url('/sites/tests/Style%20Library/demo/leftarrow-active.png');
background-repeat:no-repeat;
height: 46px;
width: 35px;
position:absolute;
top:50%;
left:0;
z-index:99;

}
#slideshowrightarrow:hover{
background:url('/sites/tests/Style%20Library/demo/rightarrow-active.png');
background-repeat:no-repeat;
height: 46px;
width: 35px;
position:absolute;
top:50%;
right:0;
z-index:99;

}

a[title="Previous"] {
display:none;
}

a[title="Next"] {
display:none;
}

/* End WebPart slideshow */

 

Et voici le résultat :

slideshowjs

 

 

[SP2013] – Récupérer le statut d’un Workflow d’Approbation en REST

Posted on

Bonjour,

Aujourd’hui, un article orienté développement. Il s’agit ici d’une fonction JavaScript permettant de récupérer le statut d’un Workflow d’approbation standard SharePoint Server 2013 afin de l’afficher dans une page par exemple.

A la base, dans mon exemple, je devais afficher ce statut d’approbation dans la page d’accueil d’un Document Set sur lequel portait ce flux d’approbation. L’idée était d’insérer, via un WebPart  éditeur de contenu, un bloc de HTML et de fonctions Javascript afin d’afficher ce statut.

Je ne vais détailler ici que la partie requêtage, il sera facile pour vous d’utiliser ceci dans une logique métier par exemple.

1/ Statuts du Flux de travail d’approbation

Première chose à connaitre, ce sont les différents statuts qui peuvent être affichés lors de l’utilisation de ce type de flux de travail. Ces statuts sont représentés par un entier :


0 == Not Started
1 == Failed on Start
2 == In Progress
3 == Error Occurred
4 == Canceled
5 == Completed
6 == Failed on Start(Retrying)
7 == Error Occurred (Retrying)
15 == Canceled
16 == Approved
17 == Rejected

 

2/ Appeler la fonction…

Pour appeler la fonction, j’ai choisi d’utiliser la fonction JQuery ready(), qui appelle ExecuteOrDelayUntilScriptLoaded() afin de différer l’appel à ma fonction qui récupère le statut, après le chargement de sp.js :


$(document).ready(function () { ExecuteOrDelayUntilScriptLoaded(loadItem, "sp.js"); });

Puis on déclare la fonction loadItem. Cette fonction récupère l’identifiant de l’item courant dans la QueryString (vous pouvez modifier cette partie suivant le cas d’usage) puis construit l’url de la requête REST à partir de cet identifiant et de l’identifiant de la Liste :

function loadItem() {
itemId = getParameterByName('ID');
console.log(WFName + ' : Current item id = ' + itemId);
listId = _spPageContextInfo.pageListId;
console.log(WFName + ' : Current list id = ' + listId);
var url = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getById('" + listId + "')/items(" + itemId + ")?$expand=FieldValuesAsHtml";
var requestHeaders = { "accept" : "application/json;odata=verbose" };
return $.ajax({
url: url,
method: "GET",
contentType: "application/json;odata=verbose",
headers: requestHeaders,
success : loaditemsuccess,
error : onError
});
}

 

Suite à l’exécution de la fonction précédente, en cas de succès, on récupère le statut du Workflow et on affiche le statut comme souhaité (non détaillé dans cet article) :


var WFColunmName = 'Validati';

var WFName = 'WF Approbation';

function loaditemsuccess(data, request) {
var moderationStatusName = data.d.FieldValuesAsHtml[WFColumnName];
console.log(WFName + ' : Moderation Status = ' + moderationStatusName);

//Change DOM into div "WFStatus" to add link to start workflow + icon
if(moderationStatusName == 'Canceled') {
//Display link to start a new worklow
displayStartNewWorkflow('Workflow has been canceled. ','');
}
else if(moderationStatusName == 'Rejected') {
//Display link to start a new worklow
displayStartNewWorkflow('Workflow has been rejected. ','');
}
else if(moderationStatusName == 'Approved') {
//Document set is already approved, display a special icon with no link
displayWorkflowApproved();
}
else if(moderationStatusName == 'In Progress') {
displayWorkflowInProgress();
}
else if (moderationStatusName == '') {
//Display link to start a new worklow
displayStartNewWorkflow('','');
}
else {
onError('Unable to load workflow status.');
}
}

 

Pour récupérer le statut du Workflow, on s’appuie donc sur la colonne de statut créée par l’association du flux d’approbation à la liste/bibliothèque. C’est donc plutôt simple, il suffit d’une requête REST (que je commence à préférer au JSOM ! 🙂 )

 

3/ Quelques fonctions utiles

Voici quelques fonctions que j’ai utilisé dans mon code :


//Function to get a parameter value from QueryString
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if(results == null){
return "";
}
else{
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
}
 

//Function to manage errors
function onError(error) {
console.log('An error occured:' + error);
}

 

Et voilà !

[SharePoint 2013] – Erreur JQuery au chargement d’une page en Edit

Posted on

Bonjour,

Aujourd’hui un tips technique sur un problème constaté lors du développement d’une fonction JQuery lorsque l’on passe en Edition sur une page.

J’ai constaté cette erreur sur un site qui a été “brandé” totalement (avec un style personnalisé avancé). Au départ, mes scrollbar (verticale notamment) ne fonctionnaient plus. Et dût au conflit entre plusieurs CSS, l’astuce a été d’écrire une fonction JQuery permettant de modifier le style à la volée pour ajouter un max-height et modifier l’overflow-x sur ma page.

Le contexte ici a peu d’importance, toutefois, ce qui est intéressant, c’est que j’ai décidé d’utiliser le contrôle EditModePanel afin d’utiliser une version de cette fonction lorsque la page est en Affichage ou en mode Edition.

Pour cela, c’est assez simple (sur un site de publication), il faut ajouter une directive Register dans la masterpage du portail :


<%@ Register TagPrefix="PublishingWebControls" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

 

Et dans la page pour le mode Affichage :


<PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Display">

...

</PublishingWebControls:EditModePanel>

 

Et pour le mode Edition :


<PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Edit">

...

</PublishingWebControls:EditModePanel>

Tout ceci fonctionne bien, les fonctions sont bien chargées suivant le contexte… Là ou cela se gate, c’est lorsque l’on souhaite appeler des fonctions utiles de Jquery. En effet, en mode Affichage, aucun probleme. En mode Edition, j’ai une erreur qui me dit “Object doesn’t support this property or method ready” ou “Object doesn’t support this property or method resize”.

Il se trouve, qu’apparemment, JQuery ne parviens pas à charger ces fonctions… je me suis simplement dit, en ajoutant un ExecuteOrDelayUntilScriptLoaded pour retarder le chargement de mes fonctions après sp.js, cela devrait marcher. Ou pas.

Je vous passe les multiples essais, échecs, etc. Dans le cas de l’Edition seulement, il faut appeler les fonctions de cette manière :


( function($) {
$(document).ready( function() { FixWorkspaceInEditMode()  } );

} ) ( jQuery );

 

Et donc en résumé :

 
<PublishingWebControls:EditModePanel runat="server" PageDisplayMode="Edit">  
     function FixWorkspaceInEditMode() { 
         ... 
     } 
     ( function($) { $(document).ready( function() { FixWorkspaceInEditMode()  } ); } ) ( jQuery ); 
     ( function($) { $(document).resize( function() { FixWorkspaceInEditMode()  } ); } ) ( jQuery ); 
 
</PublishingWebControls:EditModePanel> 

[SP2013] – implémenter un JSLink dans les Annonces – Afficher le contrôle Like

Posted on Updated on

Bonjour à tous,

Cette fois-ci je vais vous parler d’une personnalisation de SharePoint 2013 au travers des JSLink. Ces JSLink sont apparus avec SharePoint 2013 et permettent de personnaliser les affichages des données à plusieurs niveaux grâce à ce que l’on appelle le Client-Side Rendering (CSR). Vous pourrez ainsi personnaliser :

  • Les Vues (List View)
  • Les Formulaires (dispform, editform)
  • Les champs (SPFields)

Il s’agit donc ici de contrôler la manière dont ces différents éléments seront rendus dans les pages SharePoint à l’aide de JavaScript principalement. Je ne serai volontairement pas exhaustif dans cet article car il me faudrait beaucoup de temps et cela me laissera peut-être l’opportunité d’écrire de nouveaux articles.

Voici donc mon périmètre : proposer un bouton Like/Unlike dans le formulaire d’affichage d’une actualité (Dispform de la liste Announcements (TemplateID = 104) et ceci sans recourir au déploiement d’une solution SharePoint WSP, mais en réalisant les opérations seulement coté “client”, avec des droits Administrateur de site (pas de ferme).

L’idée est donc de produire et déployer un fichier JavaScript dans SharePoint et de l’utiliser en l’associant à mon formulaire Dispform, sans perdre de fonctionnalités natives ou brider le comportement de ma liste, formulaires etc.

 

1/ Activer le Like sur la liste d’Announcements (Annonces)

Rien de très compliqué ici. La plupart des administrateurs fonctionnels ou techniques de sites SharePoint sauront réaliser l’opération. Pour cela, il faut naviguer jusqu’à la liste en question, et aller dans ses Paramètres. Dans cette page, nous allons dans le menu Paramètres d’évaluation (Rating Settings) :

01

Puis d’autoriser les évaluations de type “Like” :

  1. Autoriser les éléments à être évalués (traduction approximative…) : Oui
  2. Quel type d’évaluation : Likes

02

Et bien sûr, nous validons la page avec le bouton “OK”.

 

2/ Activer le JSLink sur le formulaire Dispform de la liste Announcements

En effet, une phase de préparation est nécessaire. Les formulaires (Dispform, Editform) ne proposent pas directement le paramètres JSLink sur le composant WebPart positionné dans ces pages. C’est bien ce composant WebPart qui est chargé d’afficher le formulaire avec ses champs (entêtes et contrôles de rendu).

Pour cela, il faut se rendre dans les paramètres du site > MasterPage Gallery (dans la catégorie Web Designer Galleries) :

03

Pour ma part, je choisis de placer mon JSLink dans le sous-dossier Display Templates mais il est préférable de les placer dans un sous-dossier afin de s’y retrouver à l’avenir…

Ensuite, on Upload un fichier JavaScript presque vide (SharePoint n’accepte pas de télécharger des fichiers vides…), ici appelé “jsnewsdispform.js” :

04

05

On clique sur “OK” pour valider et on renseigne le formulaire suivant. Ici il faudra être précis !

  • Choisir le Content Type : JavaScript Display Template
  • Name : Nom du fichier JavaScript (conservez celui indiqué)
  • Title : Au choix
  • Target Control Type : Form
  • Standalone : Override
  • Target Scope : Url du site où se situe l’Application Announcements (l’url de mon site en l’occurence)
  • Target List Template ID : 104 (Template ID pour les Annonces SharePoint)

 

06

07

On valide en cliquant sur “Save”. Le fichier est enregistré (il faudra penser à le publier en version principale si la gestion des versions est activée) :

08

Copier l’url du document jsnewsdispform.js (url absolue dans un fichier texte, cela nous resservira plus tard).

3/ Modification du Dispform.aspx et ajout du lien vers le fichier JavaScript

Le prérequis sont en place, il nous faut maintenant indiquer au formulaire Dispform.aspx de notre liste d’annonces qu’il doit utiliser l’attribut JSLink… et bien sûr le faire pointer vers notre fichier JavaScript.

Pour cela, naviguez vers l’Application d’annonce créé sur votre site. Si aucune actualité n’est présente, créez en une qui nous servira pour aller sur la page Dispform.aspx.

09

Une fois fait, cliquez sur une actualité. La page s’ouvre, c’est bien la page Dispform.aspx.

10

Puis on passe en mode Edition sur la page. Pour cela, utilisez le menu Site Settings (Paramètres du site) et cliquez sur “Edit page” (Editer la page) :

11

La page se rafraichit, elle est en mode Modification. Dans le coin haut-droit du WebPart, cliquez sur “Edit Web Part” :

12

La boite à outil (Toolbox) du WebPart s’affiche. Dans la catégorie “Miscellaneous” (Divers de mémoire…), le champ JSLink est bien disponible (si vous n’avez pas réalisé les étapes précédentes, il ne sera pas disponible).

Dans ce champ JSLink, entrez (en modifiant l’url si besoin, suivant l’emplacement du fichier jsnewsdispform.js) :

~site/_layouts/15/reputation.js|~site/_catalogs/masterpage/Display%20Templates/jsnewsdispform.js

13

Notez l’emploi de “|” pour demander à SharePoint de charger plusieurs fichiers JavaScript. Et ici nous aurons besoin à la fois du fichier jsnewsdispform.js mais également reputation.js pour gérer les Likes/Unlike.

Validez en cliquant sur “OK” et sauvegardez la page :

14

A partir de ce moment, lorsque que la page dispform sera affichée, notre fichier sera chargé.

4/Implémentation du JSLink

Nous abordons ici l’aspect le plus complexe. Comme je l’ai dit au début de l’article, je ne vais pas détailler l’usage du JSLink, mais je peux déjà vous conseiller la lecture de l’article proposé par Tobias Zimmergren (et l’ensemble de ses excellents articles) ici : http://zimmergren.net/technical/sp-2013-using-the-spfield-jslink-property-to-change-the-way-your-field-is-rendered-in-sharepoint-2013 et également la série d’articles de Martin Hatch ici : http://www.martinhatch.com/2013/08/jslink-and-display-templates-part-1.html.

Ensuite, quelques précisions. L’implémentation JavaScript peut être complexe, et dans l’exemple présent, le code est loin d’être parfait et vous demandera forcément une phase d’optimisation, de refactorisation afin de l’adapter pour de la production. Une fois mes excuses présentées aux grands chefs du JavaScript, voici mon exemple qui fonctionne !

4.1 Charger les éléments nécessaires

Effectivement, pour faire fonctionner la plupart des JSLink il est bon de se documenter et de bien connaitre les Features proposées par SharePoint 2013. En particuler le MDS (Minimal Download Strategy) qui aura un impact fort sur tous vos développements JSLink. Je ne vais pas détailler non plus le pourquoi du comment, il y a des dizaines d’article qui traitent du sujet (Cf. Google & compagnie, en particulier l’article de Wictor Wilén : http://www.wictorwilen.se/the-correct-way-to-execute-javascript-functions-in-sharepoint-2013-mds-enabled-sites).

Pour pouvoir fonctionner avec et sans la fonctionnalité MDS activée, voici ce que l’on peut écrire :


//Load CSR-MDS module if feature is activated
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    RegisterInMDS();
}
else {
    RegisterLikes();
}

//Register CSR-MDS module then call template registration
function RegisterInMDS() {
    // MDS enabled site
    RegisterModuleInit(_spPageContextInfo.siteServerRelativeUrl + "/_catalogs/masterpage/Display%20Templates/jsnewsdispform.js", RegisterLikes);
    // MDS disabled site
    RegisterLikes();
}

4.2 Afficher les contrôles

Puis on appelle la fonction RegisterLikes() qui se charge d’indiquer qu’un nouveau Template de rendu est associé au champ “LikesCount”, pour le formulaire DisplayForm. J’ajoute également une ligne qui permettra de charger JQuery que j’utiliserai dans ma solution (s’il n’est pas déjà présent).
Je demande donc ici d’appeler la fonction LikeFieldViewTemplate lorsque le champ LikesCount est appelé dans un formulaire de type Dispform :


//Register Template for Number Of Like Field
function RegisterLikes () {

   // Loading jQuery from a CDN path if the local is unavailable
   (window.jQuery || document.write("< script src="//ajax.aspnetcdn.com/ajax/jquery/jquery-1.10.0.min.js">< /script>"));
   var likeFieldCtx = {};

   likeFieldCtx.Templates = {};   
   likeFieldCtx.Templates.Fields = {"LikesCount": {
	"DisplayForm": LikeFieldViewTemplate
   }};

   SPClientTemplates.TemplateManager.RegisterTemplateOverrides(likeFieldCtx);
}

NB : j’aurai également dû, et c’est la bonne pratique, déclarer un namespace pour rassembler mes fonctions.

Puis on déclare la fonction qui se chargera du rendu :


//Display Number Of Like field with custom format
function LikeFieldViewTemplate(ctx) { 
	
   var likeFieldcontext = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
	
   ExecuteOrDelayUntilScriptLoaded(function() { GetItemData(ctx, ctx.FormContext, likeFieldcontext.fieldName); },"sp.js");
	
}

Ici je récupère le contexte associé au formulaire courant. Vous trouverez sur le net plusieurs façon de réaliser cela, mais dans le contexte du Dispform, c’est la seule qui a fonctionné pour moi : utiliser SPClientTemplates.Utility.GetFormContextForCurrentField(ctx). Ensuite j’appelle la fonction GetItemData mais après que sp.js soit chargé (ExecuteOrDelayUntilScriptLoaded).

Cette fonction GetItemData permet de charger le contexte dont j’aurai besoin, en particulier les Fields qui ne sont pas affichés dans le formulaire (de manière générale, s’ils ne sont pas affichés, ils ne sont pas chargé). Hors j’ai besoin du champ “LikedBy” pour afficher le nom des personnes qui ont déjà liké cet élément.

On charge donc ici l’item (colonnes : LikedBy, ID, LikesCount et Title) et pour cela on utilise la fonction load() et executeQueryAsync() en JSOM à partir de l’ID de l’item :


function GetItemData(ctx, formContext, likeFieldName) {

   var itemId = formContext.itemAttributes.Id;
   var listId = formContext.listAttributes.Id;

   var clientContext = SP.ClientContext.get_current();
   var list = clientContext.get_web().get_lists().getById(listId);
   clientContext.load(list, 'Title', 'Id');
   var item = list.getItemById(itemId);
   clientContext.load(item, "LikedBy", "ID", "LikesCount", "Title");
   clientContext.executeQueryAsync(
      Function.createDelegate(this, 
            function(){onLoadItemDataSucceeded(item, listId, ctx, likeFieldName);}),
            Function.createDelegate(this, onLoadItemDataFailed));
}

La fonction “onLoadItemDataSucceeded()” appelée en cas de succès du chargement de l’item permet d’afficher les contrôles : smiley + bouton Like/Unlike dans la page. Ce bouton appellera une fonction JavaScript personnalisée qui se chargera d’appeler l’API sociale de SharePoint.
On stocke le html nécessaire dans une variable htmlresult. Cet HTML sera injecté grâce à JQuery dans la page.

Dans ce premier extrait de la fonction onLoadItemDataSucceeded, le but est de récupérer le nom de toutes les personnes ayant liké l’élément avec une limite imposée à 5 noms et de placer “You” en début de liste si l’utilisateur connecté l’a déjà liké. Par ailleurs on peut arriver à 6 noms si l’utilisateur connecté arrive en dernier. Mais pour moi, cela n’est pas un problème. Le fait d’indiquer que l’utilisateur connecté a déjà liké prévaut sur le nombre de “likers”.



function onLoadItemDataSucceeded(item, listId, ctx, likeFieldName) {
   var likesCount = item.get_item('LikesCount');
   var itemId = item.get_item('ID')
   var likedBy = null;
   var likers = [];
   var likerslist = '';
   var userAlreadyLike = false;
   var currentUserId = _spPageContextInfo.userId;
   var linkerslimit = 5;
   var linkerslimitOverpass = false;
	
   var htmlresult = "";
	
   //Get likes & likers on current item
   if(likesCount != 0) {
      var likedBy = item.get_item("LikedBy");
		
      if (!SP.ScriptHelpers.isNullOrUndefined(likedBy)) {
	
         for(var i=0; i < likedBy.length; i++) {
            likers[likers.length] = {
               id: String(likedBy[i].get_lookupId())
               , title: likedBy[i].get_lookupValue()
            };
            //If user in loop is current user, then display You on first row of the tooltip
            if(likers[i].id == currentUserId) {
               userAlreadyLike = true;
               likerslist = 'You' + likerslist;
            }	
            else if(i < linkerslimit){				
               likerslist += likers[i].title + "\\r";				
         }
         else linkerslimitOverpass = true;
      }
      if(linkerslimitOverpass == true) {
         //If limit of likers is overpass, then display "..." at the end of the tooltip
         likerslist += '...';
      }
   }
   else {
      likesCount = 0;
   }
}
else {
   likesCount = 0;
}   

Ce second extrait construit le HTML et l’injecte via JQuery. Le but ici est de modifier l’entête de la colonne qui est “Number of likes” par “Do you like it?” puis de remplacer l’espace vide par le fameux smiley (au survol on consulte les noms des personnes qui ont liké) et le bouton Like/Unlike :



//Change DOM of page to insert Like controls
//Get td with column name : SPBookmark_LikesCount
var headerAnchorName = 'SPBookmark_' + likeFieldName;
var fieldid = '#SPFieldLikes';
	
//get td with column header => change text
$('a[name=' + headerAnchorName + ']').parent('h3').text("If you like it");
	
//get td with field value => add controls to like/unlike etc.
//add like control with smiley, number of likes, etc.
if(likesCount == 0 || userAlreadyLike == false) {

   htmlresult += 'Like';

}
else if (userAlreadyLike == true) {
   
   htmlresult += 'Unlike';

}

   //clear field content then insert buttons, icons etc.
   $(fieldid).empty();
   $(fieldid).append(htmlresult);
}

//Function called if refresh data on like/unlike controls fails
function onLoadItemDataFailed(sender, args) {
   alert('ERROR !');
   console.log('Error when loading item data.');
}

4.3 Gérer les likes/unlikes

Enfin, il faut développer les fonctions qui gèreront les clics utilisateur sur le bouton Like/Unlike. Pour cela, il faut s’assurer que “reputation.js” soit bien chargé puis appeler la fonction “Microsoft.Office.Server.ReputationModel.Reputation.setLike()” sans oublier executeQueryAsync() pour exécuter la requête :


//set like or unlike on selected item when current user clicks on like / unlike button
function setLikeOnNews(listId, itemId, like, itemtitle) { 

   var clientContext = new SP.ClientContext();
   //register native JS for social capabilities	
   //set like. If like == true => Like item ; if like == false => Unlike item	
   EnsureScriptFunc('reputation.js', 'Microsoft.Office.Server.ReputationModel.Reputation',
      function () {                     
         Microsoft.Office.Server.ReputationModel.Reputation.setLike(clientContext,listId,itemId, like);
  
         clientContext.executeQueryAsync(
               Function.createDelegate(this, function(){onQuerySucceeded(listId, itemId, like, itemtitle);}),
	       Function.createDelegate(this, onQueryFailed));
       }
   );
}

4.4 Afficher une notification

Petit bonus, j’ai choisi d’afficher une popup non-persistante afin d’avoir un retour utilisateur et confirmer que le like/unlike a bien été pris en compte. Pour cela :


//If like function is ok (without error)
function onQuerySucceeded(listId, itemId, like, itemtitle) {
   //Display a notification and change text on like/unlike link
   var message = "";
   if(like == "true") {
      var message ="You like the item";
   }
   else if(like == "false") {
      var message ="You unlike the item ";
   }
   else {
      var message ="An error occurred. Please try again";
   }
	
   //Display a popup on top right corner to confirm like / unlike
   var extraData = new SPStatusNotificationData(
   STSHtmlEncode(''),
   STSHtmlEncode(decodeURIComponent(itemtitle)),
   _spPageContextInfo.webLogoUrl
   );
 
   var myNotification = new SP.UI.Notify.Notification(
   SPNotifications.ContainerID.Status,
   STSHtmlEncode(message),
   false,
   '',
   function () { },
    extraData
   );
 
   myNotification.Show();
	
   //refresh data on tooltip (likers, number of likes)
   refreshLikeData(listId, itemId, like, itemtitle);	
}

//If like function throws an error
function onQueryFailed(sender, args) {
   //display an error
   var message = "An error occurred when you try yo like the item. : " + args.get_message();
   var notifyId = SP.UI.Notify.addNotification(message, false);
}

4.5 Mettre à jour les contrôles après un like/unlike

Bien sûr, après que l’utilisateur ait liké/déliké (anglicisme… quand tu nous tiens), il convient de rafraichir les contrôles affichés afin d’incrémenter/décrémenter le nombre de likes, la liste des personnes ayant liké et également modifier le bouton pour pouvoir déliké si on a liké et inversement.

Pour cela, il faut recharger l’item courant en utilisant la même technique que pour le premier chargement :


//Refresh data when user is liking / unliking an item
function refreshLikeData(listId, itemId, like, itemtitle) {
   //get data from list item
   var clientContext = SP.ClientContext.get_current();
   var list = clientContext.get_web().get_lists().getById(listId);
   clientContext.load(list, 'Title', 'Id');
   var item = list.getItemById(itemId);
   clientContext.load(item, "LikedBy", "ID", "LikesCount", "Title");
   clientContext.executeQueryAsync(
         Function.createDelegate(this, function(){onRefreshLoadDataSucceeded(itemId, like, item, list);}),
         Function.createDelegate(this, onRefreshLoadDataFailed)
   );
}

Et enfin on injecte les mises à jour toujours via du JQuery (que j’aurai pu factoriser…) :



//when refresh data succeeds, change html controls like/unlike and tooltip, number of likes
function onRefreshLoadDataSucceeded(itemId, like, item, list) {
	
   //set number of likes
   $('.ms-comm-likesCount.ms-comm-reputationNumbers').text(item.get_item('LikesCount'));
	
   //change like button to unlike or unlike to like (or error) + command onclick
   if(like == "true") {
      $('a#likesElement-' + itemId).text('Unlike');
      $('a#likesElement-' + itemId).attr("onclick" , 'setLikeOnNews("' + list.get_id() + '","' + item.get_item('ID') + '","false","' + encodeURIComponent(item.get_item('Title')) + '");return false;');
   }
   else if(like == "false") {
      $('a#likesElement-' + itemId).text('Like');
      $('a#likesElement-' + itemId).attr("onclick" , 'setLikeOnNews("' + list.get_id() + '","' + item.get_item('ID') + '","true","' + encodeURIComponent(item.get_item('Title')) + '");return false;');
   }
   else {
      $('a#likesElement-' + itemId).text('ERROR');
      $('a#likesElement-' + itemId).attr("onclick" , '');
   }
	
   //change tooltip on like/unlike button
   var likers = [];
   var likerslist = '';
   var likerslimit = 5;
   var likerslimitOverpass = false;
   var currentUserId = _spPageContextInfo.userId;
   //Set likers on span
   if(item.get_item('LikesCount') != 0) {
      var likedBy = item.get_item('LikedBy');
		
      if (!SP.ScriptHelpers.isNullOrUndefined(likedBy)) {
         for (var i = 0; i < likedBy.length; i++) {
			
            likers[likers.length] = {
		id: String(likedBy[i].get_lookupId())
		, title: likedBy[i].get_lookupValue()
	    };
				
	    if (likers[i].id == currentUserId) {
               userAlreadyLike = true;
               likerslist = 'You \r' + likerslist;
            }
            else if(i < likerslimit) {
               likerslist += likers[i].title + '\r';					
            }
            else
               likerslimitOverpass = true;
            }
            if(likerslimitOverpass == true) {
               likerslist += '...\r';
            }	
         }
	else {
           likesCount = 0;
         }
      }
      else {
         likesCount = 0;
      }   	
	
   $('.ms-comm-likesMetadata.ms-metadata').attr('title',likerslist.replace(/\r$/, ""));
}

//Function called if refresh data on like/unlike controls fails
function onRefreshLoadDataFailed(sender, args) {
   //set number of likes
   $('.ms-comm-likesCount.ms-comm-reputationNumbers').text(item.get_item('ERROR'));
   $('a#likesElement-' + itemId).text('ERROR');
   $('a#likesElement-' + itemId).attr("onclick" , '');
}

Et tout devrait fonctionner ! La preuve en images… et en français :
15

16

Voici donc un exemple “assez simple” d’une personnalisation, sans code serveur, sans solution à déployer… et qui ouvre la porte à de nouvelles personnalisations.
Et je sais que certains vont me poser la question : “Mais ça y sera dans SP2016 ?” ===> La réponse est OUI 🙂

A bientôt !

[SP2013] – Accéder aux données du Secure Store Service en C#

Posted on Updated on

Bonjour à tous,

Aujourd’hui un article technique pour vous montrer comment accéder aux données stockées dans le Secure Store Service de SharePoint Server 2013.

A quoi sert ce service ? Il suffit de suivre le lien : https://technet.microsoft.com/fr-fr/library/Ee806866.aspx

1/ Contexte général

Le but étant de stocker des données de type login, mot de passe, domaine, Pin, etc. afin d’accéder à des systèmes externe à SharePoint.

Habituellement, on utilise le Secure Store Service (SSS) de SharePoint pour enregistrer les credentials utilisés pour la mise en œuvre de listes externes dans SharePoint par le biais du Business Connectivity Service (BCS, ex-BDC). imaginons le cas où l’on souhaite afficher dans SharePoint une base de clients/fournisseurs qui provient d’une base de données SQL Server. Cette base peut utiliser un mode d’authentification SQL (et non Annuaire AD) et il faut donc envoyer un login/mot de passe pour pouvoir se connecter et requêter la base depuis SharePoint. C’est donc un SSO (Single Sign-On) mais Inter-Domaine (Cross-Domain) que nous propose Microsoft. Les données de cette base d’identifiants sont chiffrées via une clé générée par SharePoint lui-même (à partir d’une phrase secrète qu’il ne faut pas perdre !).

Dans le cas présent, le but est d’accéder à ce Secure Store Service afin de stocker, lire, mettre à jour les données d’un utilisateur, et proposer par exemple un formulaire de connexion, de mise à jour de ces données directement depuis un portail SharePoint.

2/ Architecture du service

Le Secure Store Service est une Application de Service proposée par SharePoint Server 2013 (Standard ou Enterprise). Elle peut se déployer par la console d’administration centrale ou PowerShell (voir l’article Technet précédent).

La clé de chiffrement permet de chiffrer les données stockées dans la base de données associée à l’application de service.

Les données sont stockées dans des Target Applications (Applications cibles). Chaque Target application stocke les données pour 1 application, par exemple :

  • Une Target Application pour la connexion à une base SQL Server
  • Une Target Application pour la connexion à une ferme Dynamics CRM
  • Une Target Application pour la connexion à SAP
  • Une Target Application pour la connexion à une base Oracle

Chacune de ces Target Application est définie par l’administrateur SharePoint où il doit fournir :

  1. ID de l’application cible : identifiant unique de la Target Application
  2. Nom complet : nom d’affichage de la Target Application
  3. Messagerie de contact : adresse e-mail de contact pour l’administrateur de cette Target Application
  4. Url de la page d’application cible : voir article Technet
  5. Type d’application cible : il s’agit ici d’indiquer si chaque utilisateur aura sa propre identification ou si l’on va définir une identification pour tout un groupe de personnes (individual ou group).

Ensuite vous devrez spécifier les champs que vous souhaitez enregistrer : champs d’identification Windows, champs d’identification standards (login/mot de passe), code Pin, domaine, etc.

Et enfin les administrateurs de la Target Application. Bref, tout cela est plutôt bien défini dans l’article Technet.

3/ Lire des credentials depuis mon code en C#

Voici enfin le cœur du problème. Comment accéder aux données stockées dans le SSS depuis mon code ?

J’ai tout d’abord défini une classe métier pour stocker quelques informations : Login, password, Login SharePoint, Nom Complet et E-mail de l’utilisateur :


namespace MonDev.LogMe.Utils
{
   public class SSSCredentials
   {
      public string Login { get; set; }
      public string Password { get; set; }
      public string SPLogin { get; set; }
      public string SPFullName { get; set; }
      public string SPEmail { get; set; }
   }
}

Ensuite, voici le code de la méthode GetCredentials que j’utilise. Les paramètres attendus sont :

  • site : collection de sites courante
  • applicationID : identifiant (interne) de la Target Application

   /// Get credentials for current connected user from Secure Store Service.
   /// current site collection, associated to a Secure Store Service at Web Application Level
   /// Target Application ID in Secure Store Service
   /// Returns Credentials if found, null otherwise.
   public static SSSCredentials GetCredentials(SPSite site, string applicationID)
   {
      //Initialize new Credentials structure
      SSSCredentials credentials = new SSSCredentials();
      credentials.SPEmail = site.RootWeb.CurrentUser.Email;
      credentials.SPFullName = !string.IsNullOrEmpty(site.RootWeb.CurrentUser.Name) ? site.RootWeb.CurrentUser.Name : site.RootWeb.CurrentUser.LoginName;
      credentials.SPLogin = site.RootWeb.CurrentUser.LoginName;

      //new SPSite is not mandatory, but it seems to be more efficient than use SPSite from parameters.
      using (SPSite currentSite = new SPSite(site.ID))
      {
         //Initialize a new SecureProvider to access to data into SSS
         ISecureStoreProvider provider = SecureStoreProviderFactory.Create();
         if (provider == null)
            throw new InvalidOperationException("Unable to get an ISecureStoreProvider");

         ISecureStoreServiceContext providerContext = provider as ISecureStoreServiceContext;
         providerContext.Context = SPServiceContext.GetContext(currentSite);

         try
         {
            using (SecureStoreCredentialCollection creds = provider.GetCredentials(applicationID))
            {
               if (creds != null)
               {
                  foreach (SecureStoreCredential cred in creds)
                  {
                     if (cred == null)
                        continue;

                     switch (cred.CredentialType)
                     {
                        case SecureStoreCredentialType.UserName:
                           credentials.Login = GetStringFromSecureString(cred.Credential);
                           break;
                        case SecureStoreCredentialType.Password:
                           credentials.Password = GetStringFromSecureString(cred.Credential);
                           break;
                        default:
                           break;
                     }
                  }
               }
            }
         }
         catch (SecureStoreException)
         {
            //No credentials are found into SSS, so return null
            return null;
         }
         catch (Exception e)
         {
            throw new Exception("A general error occured. Please contact your administrator. Error : " + e.Message);
         }
      }
      return credentials;
   }

La méthode doit être appelée comme suit :


private const string TargetApplicationID = "MaTargetApplication";
SSSCredentials credentials = SSSUtils.GetCredentials(SPContext.Current.Site, TargetApplicationID);

Rien de très compliqué toutefois je suis tombé sur quelques exemples de codes et aucun n’a fonctionné chez moi. A noter ici, pas besoin d’impersonation ou de SPSecurity.RunWithElevatedPrivileges(), bien au contraire. Il faut se connecter au provider avec les identifiants de l’utilisateur connecté au portail pour pouvoir récupérer ses identifiants !

 

4/ Ecrire dans le Secure Store Service

Si nous avons lu, il faut bien parvenir à écrire dans le SSS. Ici la méthode est la même pour l’ajout ou la modification de credentials dans le magasin du Secure Store Service. Les paramètres attendus sont :

  • site : collection de sites courante
  • applicationID : identifiant unique (interne) de la Target Application
  • windowslogin : login Windows ou identifiant de l’utilisateur dans le portail SharePoint
  • userName : nom d’utilisateur à stocker dans le Secure Store Service, dans la Target Application
  • userPassword : mot de passe à stocker dans le Secure Store Service, dans la Target Application

 


public static void SetCredentials(SPSite site, string applicationID, string windowslogin, string userName, string userPassword)
   {
      SPClaim claim = SPClaimProviderManager.CreateUserClaim(windowslogin, SPOriginalIssuerType.Windows);
      SecureStoreServiceClaim ssClaim = new SecureStoreServiceClaim(claim);

      SPServiceContext context = SPServiceContext.GetContext(SPServiceApplicationProxyGroup.Default, SPSiteSubscriptionIdentifier.Default);

      SecureStoreServiceProxy ssp = new SecureStoreServiceProxy();
      ISecureStore iss = ssp.GetSecureStore(context);

      IList applicationFields = iss.GetUserApplicationFields(applicationID);

      IList creds = new List(applicationFields.Count);

      using (SecureStoreCredentialCollection credentials = new SecureStoreCredentialCollection(creds))
      {
         foreach (TargetApplicationField field in applicationFields)
         {
            switch (field.CredentialType)
            {
               case SecureStoreCredentialType.UserName:
                  creds.Add(new SecureStoreCredential(GetSecureStringFromString(userName), SecureStoreCredentialType.UserName));
                  break;
               case SecureStoreCredentialType.Password:
                  creds.Add(new SecureStoreCredential(GetSecureStringFromString(userPassword), SecureStoreCredentialType.Password));
                  break;
               default:
                  break;
            }
         }
         iss.SetCredentials(applicationID, credentials);
      }
   }

La méthode doit être appelée comme suit :


SSSUtils.SetCredentials(SPContext.Current.Site, TargetApplicationID, SPContext.Current.Site.RootWeb.CurrentUser.LoginName, "LOGIN_A_STOCKER", "PASSWORD_A_STOCKER");

A savoir, il est important d’exécuter le code avec le compte de l’utilisateur connecté à SharePoint afin de pouvoir lire ses credentials ou écrire/mettre à jour ses credentials. Lorsque j’essayais d’écrire le code pour faire ces manipulations, je suis tombé sur pas mal de bouts de code où ils essayaient d’exécuter cela avec le compte système via de l’impersonation ou bien SPSecurity.RunWithElevatedPrivileges(). Cela n’a pas marché pour moi !

5/ Fonctions annexes

Dans les extraits de codes précédents, j’ai utilisé quelques méthodes supplémentaire pour encoder/décoder les chaines de caractères envoyées ou reçues depuis le Secure Store Service. Pour être totalement complet, les voici.

Cette première méthode permet de décoder une chaine encodée (SecureString) dans une chaine standard (String)


internal static string GetStringFromSecureString(SecureString secStr)
{
   if (secStr == null)
      return null;

   IntPtr pPlainText = IntPtr.Zero;
   try
   {
      pPlainText = Marshal.SecureStringToBSTR(secStr);
      return Marshal.PtrToStringBSTR(pPlainText);
   }
   finally
   {
      if (pPlainText != IntPtr.Zero)
         Marshal.FreeBSTR(pPlainText);
   }
}

Et bien sûr la méthode effectuant la manipulation inverse :


internal static SecureString GetSecureStringFromString(string str)
{
   if (str == null)
      return null;
   var str2 = new SecureString();
   char[] chArray = str.ToCharArray();
   for (int i = 0; i < chArray.Length; i++)
   {
      str2.AppendChar(chArray[i]);
      chArray[i] = '0';
   }
   return str2;
}

6/ Usings…

J’ai un peu oublié d’en parler au début, mais certains peuvent avoir des difficultés pour inclure les bonnes références et using nécessaires au projet (je ne parlerai pas des assemblies standards…).

Références :

  1. Microsoft.BusinessData (à aller cherche dans) : C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.BusinessData.dll
  2. Microsoft.Office.SecureStoreService (à aller rechercher dans le GAC) : C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.Office.SecureStoreService\v4.0_15.0.0.0__71e9bce111e9429c\Microsoft.Office.SecureStoreService.dll
  3. Microsoft.Office.SecureStoreService.Server.Security (à aller cherche dans) : C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.Office.SecureStoreService.Server.Security.dll

Il ne nous reste plus qu’à trouver un usage à tout ceci ! 🙂

Florent.

[SharePoint 2016] – Quelques nouveautés annoncées à l’Ignite

Posted on Updated on

Bonjour,

Il y a quelques jours se déroulait la conférence Ignite à Chicago au cours de laquelle on apprenait l’arrivée de quelques nouveautés sur la prochaine mouture de SharePoint : SharePoint 2016 !

A priori pas de renommage de produit comme on avait pu le connaitre entre 2007 et 2010, Microsoft reste sur SharePoint Foundation et SharePoint Server.

On apprend aussi, même si cela paraissait évident, que Microsoft s’appuie sur le développement de la version Online / Office 365 pour guider les développements de la version OnPremise. Bien sûr la version Online conservera de l’avance et un lot de fonctionnalités que ne seront à priori pas proposées dans la version OnPremise.

Concernant les dates de sorties, Microsoft tablerait pour une sortie en Public Release pour Q4 2015 et une sortie officielle pour Q2 2016 avec une Release Candidate entre les deux. Donc pas tout de suite malheureusement.

Concernant les nouveautés, Microsoft frappe fort au niveau des limitations connues avec SharePoint 2010 et 2013 :

  • Taille des bases de données de contenu : supérieures à 1To
  • Nombre de collections de sites par base de contenu : 100 000
  • Seuil des listes : supérieur à 5000
  • Taille max par fichier : 10Go
  • Suppression des limitations sur les restrictions de caractères spéciaux
  • Elements indexés : 500 millions

Il est certain que d’autres limitations vont être revues à la hausse ! 🙂

Microsoft annonce aussi un Zero downtime lors les opérations de patch des fermes SharePoint, le but étant bien sûr d’éviter toute coupure de service durant ces opérations.

Toujours dans l’infra, Microsoft revoit sa copie pour la mise en œuvre d’environnements hybrides et pousse dans cette direction (par le passé, les scénarios hybrides étaient principalement évoqués pour des phases transitoires de migration). On peut donc s’attendre à un ensemble d’outils/wizard/scripts permettant d’accélérer le déploiement et la configuration des composants.

Et encore pour l’infra… la possibilité de mieux cibler les rôles de chaque serveur dans la ferme, avec sans doute à la clé une optimisation de l’adhérence des fichiers d’installation (réduction de l’empreinte disque, utilisation optimisée des ressources) :

  • Special load : applications tierces, application/service devant être isolé
  • Web Front End : serveur frontaux Web pour la navigation des utilisateurs optimisés pour réduire le temps de latence
  • Application : serveurs d’application (Applications de services SharePoint)
  • Search : serveurs Crawl, index etc. spécialisés pour le service de recherche de SharePoint 2016
  • Distributed Cache : cache distribué pour “load balancer” les requêtes entre les WFE et optimisation/cache de la navigation des utilisateurs

La configuration Single Server Farm est toujours d’actualité (Sandbox), mais j’ai entendu dire que SQL Server Express ne serait plus de la partie… il faudra donc installer SQL Server indépendamment de SharePoint.

Les versions d’OS supportés devraient être Windows Server 2012R2 et Windows Server 10 et du coté base de données, probablement SQL Server 2014 (peut être également SQL Server 2012 avec Service Pack dans sa dernière release).

Pour les méthodes de migrations, elles devraient rester les mêmes : attach/detach ou inPlace, avec l’obligation de passer par SP2013 pour ceux qui viendraient de SP2010 (et oui… toujours).

Dans les nouvelles fonctionnalités, Microsoft propose les Durable Links qui permettrait d’avoir une URL unique pour un document (associé à un ResourceID) avec un redirecteur permettant de retrouver le fichier (un peu à la manière des DocumentID de SharePoint 2010 & 2013). Ce qui permet de renommer, déplacer le fichier sans que son url ne soit modifiée.

Dans la partie Compliance, le DLP (Data Loss Prevention) qui permettra de protéger les données sensibles, et respecter les politiques d’entreprises, l’encryption etc. Un mix entre les audits, AzureRMS, eDiscovery ? à suivre.

Coté services, et plus particulièrement User Profile, FIM (ForeFront Identity Manager) déployé par le service disparaîtrait mais pourrait être couplé à un FIM installé séparément.

Coté recherche, on parle d’un mix entre FAST de SharePoint 2013 et Office Graph (en hybride donc, onPremise indexes et Cloud indexes).

Plus d’infos sur la roadmap de SharePoint 2016 : http://blogs.office.com/2015/04/16/sharepoint-server-2016-update/.

Bien sûr, des infos ont été révélées à l’Ignite, d’autres sont mes suppositions (mes espoirs !). Pour vérifier tout cela, rendez-vous au Q2 2016… VIVEMENT !

[SharePoint] – Script PowerShell pour afficher les tailles des bases de données

Posted on Updated on

Bonjour à tous,

Aujourd’hui un petit script bien utile qui vous permettra d’afficher la taille des bases de données d’une ferme SharePoint OnPremise.

Cela permettra à l’administrateur de ferme principalement de suivre l’évolution du stockage dans la ferme SharePoint, afin d’affiner ses quotas, optimiser son serveur / cluster SQL Server.

Le but est donc de créer un petit script qui parcourra les Web Applications, puis les bases de données qui leur sont associées (on ne sélectionne donc pas les bases de données des Applications de Service) et d’afficher le tout dans une GridView par exemple.

Dans cette démo, j’utiliserai l’environnement PowerShell ISE (Integrated Scripting Environment), avec Windows Server 2012 et une plateforme SharePoint Server 2013 Enterprise (Pré-SP1).

Première étape, lancer PowerShell ISE avec le compte Administrateur de la ferme SharePoint :

02

Et entrer le script tout fait 🙂

Ok, je vais détailler un peu son contenu :

Première étape, afin que l’on puisse lancer le script avec PowerShell, sans utiliser le SharePoint Management Shell, on ajoute l’import des Cmdlets SharePoint :

If ((Get-PSSnapIn -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) 
{ Add-PSSnapIn -Name Microsoft.SharePoint.PowerShell }

$host.Runspace.ThreadOptions = "ReuseThread"

Puis on démarre un scope pour les objets utilisés dans le script afin qu’ils soient automatiquement “Disposés” à la fin de l’exécution. Pour cela, nous allons utiliser les commandes (au début et à la fin du script) :

Start-SPAssignment –Global
Stop-SPAssignment –Global

Plus de détails ici : http://technet.microsoft.com/fr-fr/library/ff607664(v=office.15).aspx

On commence ensuite la construction dun tableau (Hashtable plus particulièrement) en parcourant les WebApplications, les bases de contenu et en calculant leur taille en GB :

$webApps = Get-SPWebApplication -IncludeCentralAdministration
foreach($WebApp in $spWebApps)
{
    $ContentDBs = $webApp.ContentDatabases
    foreach($ContentDB in $ContentDBs)
    {    
        $size = [Math]::Round(($ContentDB.disksizerequired/1GB),2)

        $DBdetails = New-Object PSObject
        $DBdetails | Add-Member -Name "Web Application Name" -MemberType NoteProperty -Value $WebApp.DisplayName
        $DBdetails | Add-Member -Name "Database Name" -MemberType NoteProperty -Value $ContentDB.Name
        $DBdetails | Add-Member -Name "Database Size (in Gb)" -MemberType NoteProperty -Value $size         $data += $DBdetails
    }
}

Puis pour terminer, on affiche le tout dans une GridView :

$DB = $data | Out-GridView -Title "SharePoint Databases Size" –PassThru

Il nous faut ajouter un try/catch pour gérer les exceptions… ce qui donne au final :

03

Puis on lance l’exécution du script (sauvegardé dans un fichier PS1) ou avec le bouton “Play” de PowerShell ISE :

05

Et voilà !