Développer un event handler pour WSS 3/MOSS 2007

Stéphane Eyskens

Ce tutoriel illustre comment créer un event handler (gestionnaire de procédures évènementielles attachées à une liste) dans WSS 3/MOSS 2007

- Titre : Développer un event handler pour WSS 3/MOSS 2007

- Auteur : Stéphane Eyskens

- Parution : 1 juin 2007

- Licence : Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.

Sommaire

- I. Introduction

- II. Développement

- II-A. Les évènements intervenant sur des éléments de liste

- II-A-1. Evènements synchrones

- II-A-2. Evènements asynchrones

- II-B. Séquençage des évènements

- II-C. Création du projet et premiers pas

- II-D. Propriétés intéressantes

- II-E. La gestion d'erreurs

- II-F. Déboguer un event handler

- II-G. Eviter de se mordre la queue

- II-H. Notre code

- II-I. Signature de l'assemblage

- II-J. Automatiser l'exécution du fichier bat

- III. Associer l'event handler à une liste

- III-A. Associer par le code

- III-B. Associer à l'aide d'un outil tiers

- III-C. Visualiser les évènements existants

- IV. Téléchargement

I - Introduction

Un event handler est une librairie que l'on déploie sur le serveur Sharepoint et que l'on attache à une liste pour déclencher une ou plusieurs actions lorsqu'un élément de cette liste est ajouté/modifié/supprimé. Il y a une multitude de cas d'utilisation possible. Je dirais qu'on peut les utiliser pour presque tout sauf pour des actions nécessitant une IHM et donc une intervention humaine quelconque (approbation de document par ex). Pour ce cas précis, on préfèrera développer un workflow.

Pour ce tutoriel, nous allons développer un Event Handler qui contrôlera la taille du document que nous chargerons dans la liste et qui annulera l'insertion en cas de dépassement d'une certaine limite. Par ailleurs, et pour gérer plus qu'un seul évènement, il ajoutera un élément d'audit dans une liste prévue à cet effet à chaque fois qu'un élément de la libraire de document est supprimé. Il ajoutera également une annonce dans la liste d'annonces prévue à cet effet lorsqu'un document aura été ajouté avec succès dans la librairie.

II - Développement

Il existe deux types d'évènement dans les event handlers. Les évènements synchrones et asynchrones. Les évènements synchrones nous permettent d'intervenir lors d'opérations en cours comme l'addition, la suppression, ou la modification d'un élément et nous permettent le cas échéant d'interrompre le processus.

Les évènements asynchrones interviennent lorsque l'opération sur laquelle on désire intervenir est terminée. Ils ne peuvent donc pas servir à interrompre une opération.

II-A - Les évènements intervenant sur des éléments de liste

Tous les évènements ci-dessous sont des membres de SPItemEventReceiver

II-A-1 - Evènements synchrones

ItemAdding

Intervient lorsqu'un nouvel élément est ajouté dans une liste

ItemAttachmentAdding

Intervient avant l'addition d'une pièce jointe.

ItemAttachmentDeleting

Intervient avant la suppression d'une pièce jointe

ItemCheckingIn

Intervient avant le check-in d'un document. Le check-in consiste à valider une modification préalablement effectuée.

ItemCheckingOut

Intervient avant la mise en check-out d'un fichier. Le check-out consiste à s'accaparer une copie d'un fichier que l'on va pouvoir modifier. Celui-ci sera en état brouillon tant qu'il n'aura pas été validé par un check-in

ItemDeleting

Intervient avant la suppression d'un document.

ItemFileMoving

Intervient avant le déplacement d'un document.

ItemUnCheckingOut

Intervient avant l'annulation d'un check-out de document.

ItemUpdating

Intervient avant la mise à jour d'un élément.

II-A-2 - Evènements asynchrones

ItemAdded

Intervient après l'ajout d'un élément.

ItemAttachementAdded

Intervient après l'addition d'une pièce jointe par upload ou par ajout direct.

ItemAttachementDeleted

Intervient après la suppression d'une pièce jointe.

ItemCheckedIn

Intervient après le check-in d'un document.

ItemCheckedOut

Intervient après le check-out d'un document.

ItemDeleted

Intervient après la suppression d'un élément.

ItemFileMoved

Intervient après le déplacement d'une pièce jointe.

ItemUncheckedOut

Intervient après l'annulation d'un document en check-out.

ItemUpdated

Intervient après la modification d'un élément.

II-B - Séquençage des évènements

Même si cela paraît évident, le séquençage des évènements ne se produit pas toujours forcément comme on pourrait le croire. Par exemple, lorsque l'on ajoute un nouveau document dans une librairie de documents, les évènements ItemUpdating et ItemUpdated se produisent alors qu'on aurait pu croire que seuls les évènements ItemAdding et ItemAdded seraient déclenchés. Il est donc nécessaire de bien comprendre la séquence d'évènements afin d'éviter toute collision. Vous trouverez un petit programme que j'ai réalisé dans la section téléchargement qui crée un fichier log dans c:\temp au format HTML et qui ajoute une ligne dans ce fichier dès qu'un évènement est déclenché. Il vous sera alors facile de comprendre le séquençage.

J'ai aussi créé une petite application console qui permet d'associer tous les évènements de l'assemblage pour une liste donnée. Vous n'aurez qu'à lire le fichier lisezmoi.txt pour voir comment l'utiliser (très simple)

II-C - Création du projet et premiers pas

A l'heure où j'écris ce tutoriel, il n'existe pas de template visual studio pour les event handlers mais rassurez-vous, c'est très simple. Voici les étapes basiques pour démarrer

- Créez un nouveau projet de type "Class Library"

- Ajoutez la référence Microsoft.Sharepoint.dll qui se trouve généralement dans c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\isapi

- Ajoutez la directive using Microsoft.Sharepoint

- Faites dériver votre classe de SPItemEventReceiver

Après ces étapes basiques, vous êtes réellement prêt à démarrer l'écriture de votre event handler.

II-D - Propriétés intéressantes

BeforeProperties

Expose les colonnes internes telles que title, filesize et les colonnes personnelles avec leur ancienne valeur. On peut par exemple comparer les valeurs de cette collection avec celles de AfterProperties pour vérifier si tel ou tel champ a été modifié. Cette collection est en lecture seule.

AfterProperties

Expose les colonnes internes telles que title, filesize et les colonnes personnelles avec leur nouvelle valeur. Cette collection est accessible en lecture-écriture. On peut donc l'utiliser pour attribuer des valeurs à certains champs. Si vous désirez créer un champ calculé personnel par exemple qui dépasse les limites d'un champ calculé que Sharepoint permet de faire.

ListItem

Expose toutes les colonnes de la liste. Est accessible en lecture-écriture et n'est disponible que pour les évènements asynchrones

WebUrl

Expose l'url du site contenant la liste qui déclenche l'évènement

BeforeUrl et AfterUrl

Contiennent respectivement l'url de l'item avant et après l'exécution de l'évènement. Lors d'une suppression par exemple, AfterUrl sera vide

Cancel

Mise à True, cette propriété permet d'avorter une opération. Elle n'est disponible que pour les évènements synchrones.

ErrorMessage

Permet d'afficher un message d'erreur personnel à l'utilisateur dans Sharepoint.

UserLoginName et UserDisplayName

Contiennent respectivement le nom de l'utilisateur et son login

II-E - La gestion d'erreurs

Un event handler n'est autre qu'une DLL classique mais celle-ci sera exécutée par Sharepoint. Toute erreur provoquée par votre code ne stoppera pas une opération en cours. Donc, si par exemple, un nouvel élément est ajouté à une liste et que vous avez défini un event handler sur l'ajout d'élément qui provoque une erreur, l'élément sera tout de même ajouté à la liste et un Event Log sera automatiquement créé par Sharepoint pour rapporter votre erreur

Vous pouvez néanmoins utiliser les try/catch/finally comme pour n'importe quelle autre DLL. Simplement, si vous ne contrôlez pas les exceptions pouvant survenir dans votre DLL, la couche supérieure de Sharepoint fera un catch et loggera l'évènement dans l'event viewer

II-F - Déboguer un event handler

Si vous êtes sur le serveur, vous pouvez directement déboguer à l'aide de visual studio. Vous devez ouvrir votre event handler, placer un point d'arrêt (par ex dans le constructeur) et attacher Visual Studio à l'un des processus w3p. Pour ce faire, procédez comme suit:

- Si vous pouvez faire ce que vous voulez sur le serveur, je vous conseille d'arrêter tous les pools d'application dans IIS excepté celui qui exécute votre application Sharepoint

- Ensuite, ouvrez votre projet dans visual studio

- Cliquez sur Debug->Attach to process

- Localisez le process W3p (si vous avez fait l'étape 1, vous ne devriez en avoir que un ou deux)

- Cliquez sur Attach

- Allez dans l'interface de Sharepoint et provoquez une action supposée déclencher votre event. Normalement, Visual Studio devrait clignoter et vous permettre de déboguer pas à pas.

II-G - Eviter de se mordre la queue

Si vous écrivez un event handler qui se déclenche sur la modification d'un élément et que dans votre event, vous modifiez à votre tour l'item courant (sa sécurit par exemple), vous allez involontairement redéclencher votre event puisque vous aurez procédé à une mise à jour. Pour éviter de se mordre la queue, Sharepoint permet de désactiver le déclenchement des event handlers et de les réactiver.

Pour les désactiver, vous pouvez utiliser DisableEventFiring et pour les réactiver vous pouvez utiliser EnableEventFiring. Sachez néanmoins que la réactivation des events est automatiquement rétablie lorsque votre event se termine.

II-H - Notre code

Tous les commentaires intéressants se trouvent dans le code.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.SharePoint;

namespace DemoEventHandler
{
public class Demo : SPItemEventReceiver
{
const int MaxFileSize = 5000; //Taille maximale de 5kb

/// <summary>
/// Cet évènement est déclenché lorsque le document est en passe d'être ajouté à la liste
/// </summary>
/// <param name="properties"></param>
public override void ItemAdding(SPItemEventProperties properties)
{
//Capture de la taille du fichier uploadé
int ItemFileSize = Convert.ToInt16(properties.AfterProperties["vti_filesize"].ToString());
//Si elle dépasse la taille maximale autorisée
if (ItemFileSize > MaxFileSize)
{
//On affiche un message d'erreur
properties.ErrorMessage = "Le fichier que vous tentez de charger dans la liste est trop gros, max " +
MaxFileSize.ToString();
//On annule l'opération, l'élément ne sera donc pas ajouté.
properties.Cancel = true;
}

}
/// <summary>
/// Cet évènement est déclenché lorsque le document a été ajouté à la liste.
/// </summary>
/// <param name="properties"></param>
public override void ItemAdded(SPItemEventProperties properties)
{
//Construction de l'annonce en récupérant les propriétés Name et l'URL du document uploadé.
StringBuilder MessageBody = new StringBuilder();
MessageBody.Append("Le document ");
MessageBody.Append(properties.ListItem["Name"]);
MessageBody.Append(" créé par ");
MessageBody.Append(properties.ListItem["Created By"]);
MessageBody.Append(" est disponible <a href='");
MessageBody.Append(properties.ListItem["EncodedAbsUrl"]);
MessageBody.Append("'>ici</a>");
AddToList("Announcements", MessageBody,properties.WebUrl,"Body","Nouveau document!");
}

/// <summary>
/// Cet évènement est déclenché lorsqu'un document est supprimé
/// </summary>
/// <param name="properties"></param>
public override void ItemDeleted(SPItemEventProperties properties)
{
//Ajout d'un élément dans la table AuditDocs qui dit quel document a été supprimé et par qui
StringBuilder MessageBody = new StringBuilder();
MessageBody.Append("L'élément ");
MessageBody.Append(properties.ListItemId);
MessageBody.Append(" ");
MessageBody.Append(properties.BeforeUrl);
MessageBody.Append(" a été supprimé par ");
MessageBody.Append(properties.UserDisplayName);
AddToList("AuditDocs",MessageBody,properties.WebUrl,"Title","");
}
/// <summary>
/// Ajout d'un élément dans la liste "ListName"
/// </summary>
/// <param name="ListName"></param>
/// <param name="Message"></param>
/// <param name="Url"></param>
/// <param name="FieldName"></param>
/// <param name="Title"></param>
private void AddToList(string ListName,StringBuilder Message, string Url,string FieldName,string Title)
{
SPSite Site = null;
SPWeb Web = null;
try
{
Site = new SPSite(Url);
Web = Site.OpenWeb();
SPList AuditList = Web.Lists[ListName];

SPListItem AuditItem = AuditList.Items.Add();
if (Title != String.Empty)
{
AuditItem["Title"] = Title;
}
AuditItem[FieldName] = Message.ToString();
AuditItem.Update();
}
finally
{
if (Web != null)
Web.Close();
if (Site != null)
Site.Close();
}
}

}

}

Pour rendre le code ci-dessus utilisable, il faut compiler le projet de type "Class Library" et ensuite le déployer dans la GAC. Les étapes de déploiement sont expliquées ci-dessous.

II-I - Signature de l'assemblage

La première chose à faire pour pouvoir déployer son assemblage dans la GAC (global assembly cache) est de le signer. Pour cela, sélectionnez votre projet dans l'explorateur de solution de Visual Studio et cliquez sur "Properties", ensuite sur l'onglet "Signing". Vous devriez obtenir ceci

Signature de l'assemblage

II-J - Automatiser l'exécution du fichier bat

L'utilisation d'un fichier bat facilite grandement le déploiement automatique de notre DLL dans la GAC. Il suffit donc d'ajouter un fichier à notre projet contenant les lignes suivantes:

"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -uf DemoEventHandler
"%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -if bin\Debug\DemoEventHandler.dll
iisreset

La première ligne désinstalle notre DLL de la GAC, la deuxième le réinstalle et la troisième redémarre IIS. Notez qu'il peut être préférable de redémarrer uniquement le pool d'application comme expliqué ici

Pour automatiser l'exécution du fichier bat, allez dans les propriétés de votre projet et remplissez le post-build event comme illustré ci-dessous.

Liaison avec le fichier bat

Après cette opération, votre event handler sera automatiquement déployé dans la GAC dès que vous compilerez votre projet.

III - Associer l'event handler à une liste

L'API Sharepoint nous donne la possibilité d'associer notre assemblage à une ou plusieurs listes. Il n'y a en effet aucune limite en matière d'association. Vous pouvez dès lors associer le même assemblage à trois listes différentes par exemple.

Depuis WSS 3 et MOSS 2007, il n'y a plus d'interface prévue dans la centrale d'administration pour associer un assemblage à une liste. Voici comment effectuer l'association

III-A - Associer par le code

SPSite Site = new SPSite("urldusite");
SPWeb Web = Site.OpenWeb();
Web.Lists["LaListeCible"].EventReceivers.Add(SPEventReceiverType.<Type d'évènement>,Signature de l'assemblage,Nom de la classe);

Ce qui donne dans un cas concret

string AssemblySignature="DemoAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=74cdd0bb8f510c15";
string ClassName="DemoClass";
Web.Lists["Shared Documents"].EventReceivers.Add(SPEventReceiverType.ItemAdding,AssemblySignature,ClassName);
Web.Lists["Shared Documents"].EventReceivers.Add(SPEventReceiverType.ItemAdded,AssemblySignature,ClassName);

Vous noterez que si vous implémentez plusieurs évènements dans un seul et même assembly, vous devrez néanmoins associer les évènements un par un à la liste.

III-B - Associer à l'aide d'un outil tiers

Etant donné que Microsoft n'a pas délivré d'outil spécifique pour gérer les event handlers mais offre une API permettant de les gérer, quelques sociétés ont déjà développé des DemoWare, Shareware etc...Parmi eux, il y a l'event handler explorer. Il ne fait rien d'autre que ce qui est montré ci-dessus mais c'est plus pratique.

III-C - Visualiser les évènements existants

Si vous désirez connaître les évènements déjà présents pour une liste donnée, vous pouvez soit utiliser l'outil dont je viens de parler, soit l'API comme ceci:

SPSite Site = new SPSite("urldusite");
SPWeb Web = Site.OpenWeb();
foreach (SPEventReceiverDefinition def in Web.Lists["Shared Documents"].EventReceivers)
{
Console.WriteLine("Type {0} Assemblage lié {1]",def.Type,def.Assembly);
}

IV - Téléchargement

Vous pouvez télécharger l'event handler principal donné en exemple ici Un fichier lisezmoi.txt l'accompagne pour vous expliquer comment l'utiliser

Vous pouvez télécharger l'event handler permettant de comprendre la séquence des events ici

Vous pouvez télécharger le programme console permettant de lier l'event handler de séquençage à une liste de votre serveur Sharepoint ici Un fichier lisezmoi.txt accompagne également le programme pour vous aider à l'utiliser.