Background
Je suis récemment intervenu sur un projet SharePoint dans une très grande entreprise. Pour faire court, dans cette entreprise l'équipe intranet en charge de la ferme SharePoint fournit une collection de sites aux équipes métier qui en font la demande. De par l'architecture de SharePoint, l'équipe métier est alors autonome pour gérer sa collection de sites.
Une de ces équipes m'a sollicité pour les aider à la mise en place de leur projet de portail. La collection de sites comprend un sous-site visible par tout les utilisateurs, et un sous-site par pays (chaque utilisateur a accès au site de son pays, + quelques autres pour les responsables par continent) dont l’administration serait déléguée à une équipe dans chaque pays. Mon client m'a donc demandé de développer une page d'accueil dynamique qui proposerait des liens pour chacun des sous-sites accessibles par l'utilisateur courant, avec une mise en forme classique : titre, logo et la description du sous-site.
Sans hésitation, j’explique qu’il suffit de développer une web part dédiée qui utiliserait le modèle objet de SharePoint pour identifier les sous-sites auxquels l’utilisateur a accès, et qu’il y en aurait au grand maximum pour une journée. En effet, dans le cadre de mon intervention de 5 jours, le temps de développement se devait d’être court.
Mon client valide l’idée et je contacte l’équipe intranet pour vérifier que le déploiement d’un package .wsp ne poserait pas de problème. Manque de chance, c’était sans compter sur la longueur de la procédure de validation, en particulier durant cette période des vacances. Nous sommes vite arrivés à la conclusion qu’il était risqué de miser sur un déploiement en production en moins de 5 jours :
- Rédaction d’un cahier des charges “officiel”
- Réunion avec l’équipe intranet pour discuter du cahier des charges et de l’impact du développement sur la ferme
- Développement et packaging .wsp de la web part en question
- Revue de code par l’équipe intranet
- Déploiement en environnement de recette
- Tests + validation par l’équipe intranet
- Tests + validation par l’équipe métier (avec la possibilité d’un retour à la case départ)
- Déploiement en production (nocturne)
Tout cela est 100% justifié : il faut garder à l’esprit qu’un bug ou une faille dans un développement SharePoint, aussi insignifiant soit-il, peut mettre en péril la stabilité de la ferme SharePoint, et l’intégrité des données de la majeure partie de l’intranet. De plus cette procédure devrait être répétée à chaque fois qu’une modification du composant serait nécessaire dans le futur. Le jeu en valait-il vraiment la chandelle ?
Non, surtout qu’en appelant les web services SharePoint par JavaScript, on peut obtenir un résultat équivalent. C’est une approche moins classique, mais qui permet de gagner du temps sur le packaging, le déploiement et la maintenance du composant. L’appel au web service sera grandement facilité par l’utilisation de jQuery, le framework JavaScript à la mode qui dispose d’une méthode dédiée facile d’utilisation, et surtout cross-browser.
Implémentation
Avant d’aller plus loin il fallait valider qu’un web service pouvait me fournir les informations dont j’avais besoin. Le SDK de SharePoint 2007 détaille chaque web service disponible : http://msdn.microsoft.com/en-us/library/ms445760.aspx
Webs.asmx dispose d’une méthode GetWebCollection qui retourne la liste des sous-sites du site courant avec leurs titres et url, et GetWeb.asmx retourne le titre, le code langue et la description d’un site donné grâce à l’url passé en paramètre. Bingo !
J’ai référencé dans la master page du site les fichiers JavaScript (celui de jQuery et le mien) placés dans la bibliothèque “Styles Library”, et inséré sur la page d’accueil une web part de contenu éditable contenant :
- une balise (qui accueillera le menu)
- une ligne de JavaScript pour exécuter ma méthode de remplissage de cette balise.
Pour l’édition du fichier .js, on peut se servir de SharePoint Designer pour naviguer facilement jusqu’à l’emplacement de notre fichier javascript et de le modifier directement, tout en bénéficiant d’une reconnaissance syntaxique du JavaScript.
Ensuite, sur une machine virtuelle MOSS 2007, on peut récupérer l’enveloppe soap correspondante à la méthode que l’on souhaite appeler à l’aide de son navigateur. En l’occurrence, j’ai saisi l’url “http://localhost/_vti_bin/Webs.asmx” dans mon navigateur, et cliqué sur le lien de la méthode GetWebCollection :
On peut alors écrire la partie JavaScript. La méthode FillMenu() appellera le web service en utilisant l’enveloppe SOAP que l’on vient d’obtenir et remplira le menu. Voici le contenu du fichier JavaScript :
1:
2: var wsWebsUrl;
3:
4: function FillMenu(){
5:
6: wsWebsUrl = GetWebServiceUrl("Webs.asmx");
7:
8: if(wsWebsUrl != null){
9:
10: var request = " \
11: \
12: \
13: \
14: ";
15:
16: $.ajax({
17: url: wsWebsUrl,
18: type: "POST",
19: dataType: "xml",
20: data: request,
21: success: FillMenu_GetWebCollectionHandler,
22: contentType: "text/xml; charset=\"utf-8\"",
23: async:true
24: });
25:
26: }
27: }
28:
29: function FillMenu_GetWebCollectionHandler(response){
30:
31: var menuRows = "";
32:
33: // Pour chaque sous élément Web
34: $(response).find("Web").each(function() {
35:
36: var webTitle = $(this).attr("Title");
37:
38: if(webTitle != "Group"){ //On ne souhaite pas proposer de lien vers Group
39:
40: var webUrl = $(this).attr("Url");
41:
42: //récupération des informations relatives au site cible
43: var webInfos = FillMenu_GetWebInfos(webUrl)
44: if(webInfos != null){
45:
46: var webDescription = $(webInfos).attr("Description");
47: var webLogo = webUrl + "/images/logo.png"; //les logos sont stockée dans chaque site
48:
49: // On ajoute une entrée au menu
50: menuRows += " \
51: \ |
52: \
53:
\
54: \
55: \
56: \ |
57: " + webTitle + " \
58: " + webDescription + "
\
59: \
60: ";
61: }
62: }
63: });
64:
65: //on cache, on remplit, et on fait apparaitre le menu avec une animation
66: $("#testWS").hide().append(" + menuRows + "").slideDown();
67:
68: }
69:
70: function FillMenu_GetWebInfos(webUrl){
71:
72: var requestWebs = " \
73: \
74: \
75: \
76: ";
77:
78: var returnValue = null;
79:
80: //appel synchrone qui ne réussit que si l'utilisateur a le droit d'accès au site cible
81: $.ajax({
82: url: webUrl + "/_vti_bin/Webs.asmx",
83: type: "POST",
84: dataType: "xml",
85: data: requestWebs,
86: contentType: "text/xml; charset=\"utf-8\"",
87: async:false,
88: success:function(response){
89:
90: //construction de la requete soap avec insertion de l'url du site en paramètre
91: var requestWeb = " \
92: \
93: \
94: " + webUrl + " \
95: \
96: \
97: ";
98:
99: //appel synchrone pour récupérer les informations du site cible
100: $.ajax({
101: url: wsWebsUrl,
102: type: "POST",
103: dataType: "xml",
104: data: requestWeb,
105: contentType: "text/xml; charset=\"utf-8\"",
106: success:function(response){
107:
108: returnValue = $(response).find("Web");
109: },
110: async:false
111: });
112:
113: }
114:
115: });
116:
117: return returnValue;
118:
119: }
120:
121: // Récupération de l'url du web service dans le contexte du site courant
122: function GetWebServiceUrl(filename){
123:
124: // inspiré par SharePoint Bookmarklets : http://imtech.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=19047
125: var UrlPart={Url:0,ServerRelativeUrl:1,Site:2,SiteCollection:3};
126: var urlRegex=/^https?:\/\/[^\/]+(((\/(?:sites\/[^\/]+\/)?).*?)((lists\/([^\/]+)|pages|_catalogs|_layouts|_wpresources|_vti_bin|([^\/]+)\/forms|[^\/]+\.aspx).*)?)$/i;
127: var url=window.location.href.toLowerCase();
128: var match=urlRegex.exec(url);
129:
130: if( match == null)
131: return null
132: else
133: return match[UrlPart.Site] + "_vti_bin/" + filename;
134:
135: }
On notera l’utilisation d’un appel asynchrone au web service pour la récupération des sous-sites et d’un appel synchrone pour la récupération de la description d’un site. Cet appel synchrone était préférable dans mon cas pour pouvoir remplir ma grille au fur et à mesure.
Voici le résultat :
Edit : Un appel à GetWebCollection sur chaque sous-site est nécessaire pour déterminer si l’utilisateur courant y a accès.Le security-trimming n’est pas aussi efficace que je le pensais. Le code a été modifié en fonction.
Conclusion
Au final, j’ai gagné un temps fou. Certes, le menu apparait quelques dixièmes de secondes après que la page soit chargée, mais j’ai pu effectuer ce développement sans déployer de code sur la ferme. La maintenance est facilitée, et les risques d’impacts négatifs sur les serveurs sont nuls : Au pire des cas, l’utilisateur aura une erreur JavaScript.
Les web services SharePoint permettent d’obtenir la plupart des informations dont on a besoin lors d’un développement classique, de manipuler les listes, leurs éléments (insertion, modification, suppression), et bien plus encore. Il y a même un web service pour faire de la correction orthographique!
Après avoir vu la vidéo sneak peak de SharePoint 2010 pour les développeurs, en particulier la démo sur l’intégration d’applications Silverlight, on devine que les web services qui composent le “modèle objet client” seront enrichis et améliorés (WCF). On constate que la stratégie de Microsoft est de limiter l’impact d’un développement sur une ferme SharePoint, et de donner plus d’autonomie aux développeurs en réduisant les interactions nécessaires avec les administrateurs de ferme. Les développeurs pourront déployer eux-même leurs applications Silverlight sans risque. En effet, si un développement ne comporte pas de code serveur et qu’il n’interagit avec les données que via des web services qui prennent en compte la sécurité, il n’y a aucun risque pour la santé de la ferme et l’intégrité de ses données. SharePoint pourra alors devenir la plateforme de choix pour toutes les applications intranet, et le coeur du système d’information de l’entreprise.
Il est déjà possible dans SharePoint 2007 d’héberger des applications Silverlight, mais cela nécessite l’intervention d’un administrateur de ferme pour effectuer les modifications nécessaires sur la ferme.
JavaScript est à mon sens l’équivalent dans SharePoint 2007 de ce que sera Silverlight dans SharePoint 2010 en terme de facilité d’intégration. Alors autant s’y mettre tout de suite car les développements 100% client utilisant les web services pour manipuler les données, c’est l’avenir !
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 :