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
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.
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.