Les Event Receivers

Dieudonne N'Tamack

Les Event Receivers sont des actions qui vont se déclencher à une réponse à un certain type d'évènement.

Cet article se propose de vous donner un aperçu de ce que sont les Event Receivers et comment les manipuler.

- Titre : Les Event Receivers

- Auteur : Dieudonne N'Tamack

- Parution : 29 septembre 2008

- Mise à jour : 29 septembre 2008

- Licence : Copyright ® 2008 Dieudonné N'TAMACK. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 0. Tour d'horizon

- I. Création d'un Event Receiver

- I-A. Au niveau schéma de liste

- I-B. Au niveau éléments de liste

- I-C. Au niveau de notre site web, de l'envoi d'email et de la workflow library

- II. Liaison déclarative de notre Event Receiver avec une liste

- III. Liaison dynamique de notre Event Receiver avec une liste

- IV. Déploiement d'un Event Receiver

- V. Points Supplémentaires

- V-A. Attention aux évènements qui se déclenchent eux-mêmes !

- V-B. Workflow et Event Receiver Part 1

- V-C. Workflow et Event Receiver Part 2

- VI. Conclusion

0 - Tour d'horizon

Avec wss 3 / moss 2007, on va pouvoir manipuler des Event Receivers au niveau des bibliothèques de documents, mais également au niveau des listes classiques, des sites, et des actions de nos utilisateurs.

On va rencontrer 2 types d'Event Receivers ; les Event Receivers synchrones, s'exécutant avant une action, et les Event Receivers asynchrones, s'exécutant après l'action, dans un nouveau thread afin de ne pas bloquer le système.

Pour les Event Receivers synchrones, les méthodes correspondant se termineront toutes par -ing (par exemple itemAdding, itemDeleting, .), tandis que pour les Event Receivers asynchrones, les méthodes se termineront par -ed (par exemple itemAdded, itemDeleted).

En plus de pouvoir gérer les évènements au niveau de l'élément, on va également pouvoir gérer les évènements au niveau du schéma de nos objets ; au niveau de nos listes et doclibs par exemple, nous allons pouvoir capturer des évènements au moment de la création/modification/suppression d'éléments mais également au moment de la création/modification/suppression de colonnes

On va également pouvoir gérer nos Event Receivers de 2 façons, de manière déclarative ou dynamique.

I - Création d'un Event Receiver

Pour créer un Event Receiver, il nous suffit de créer une classe dérivant du type d'événement que nous souhaitons capturer.

I-A - Au niveau schéma de liste

Pour créer un Event Receiver au niveau du schéma de notre liste, nous allons créer une classe qui dérive de SPListEventReceiver.

De cette façon, nous serons capables d'intercepter tous les évènements d'ajout/modification/suppression au niveau des colonnes de notre liste.

Par exemple, le code suivant nous permet de créer un Event Receiver de liste, et y empêche toute modification de schéma.

//ListEventReceiver.cs
using System;
using Microsoft.SharePoint;

namespace Coforcert.Events.EventReceivers {
public class ListEventReceiver : SPListEventReceiver {
public override void FieldAdding(SPListEventProperties properties) {
string msg = string.Format("{0} vous n'avez pas le droit de modifer le champ {1} de la liste {2}",properties.UserDisplayName,
properties.FieldName, properties.ListTitle);
properties.Cancel = true;
properties.ErrorMessage = msg;
}

public override void FieldDeleting(SPListEventProperties properties) {
string msg = string.Format("{0} vous n'avez pas le droit de modifer le champ {1} de la liste {2}", properties.UserDisplayName,
properties.FieldName, properties.ListTitle);
properties.Cancel = true;
properties.ErrorMessage = msg;
}
public override void FieldUpdating(SPListEventProperties properties) {
string msg = string.Format("{0} vous n'avez pas le droit de modifer le champ {1} de la liste {2}", properties.UserDisplayName,
properties.FieldName, properties.ListTitle);
properties.Cancel = true;
properties.ErrorMessage = msg;
}
}
}

La propriété ErrorMessage va nous permettre de définir le message qui sera affiché à l'utilisateur qui effectuera l'action.

I-B - Au niveau éléments de liste

Pour créer un Event Receiver au niveau des éléments de notre liste, nous allons créer une classe qui dérive de SPItemEventReceiver.

De cette façon, nous serons capables d'intercepter tous les évènements d'ajout/modification/suppression au niveau des éléments que contient notre liste.

I-C - Au niveau de notre site web, de l'envoi d'email et de la workflow library

Pour un Event Receiver au niveau de notre site web, il faudra dériver de la classe SPWebEventReceiver

On peut également capter les mails que recevront nos listes, via les Event Receivers dérivant de la classe SPEMailEventReceiver, comme ceci :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;

namespace Coforcert.Events.EventReceivers
{
public class MailEventReceiver : SPEmailEventReceiver
{
public override void EmailReceived(SPList list,SPEmailMessage emailMessage, string receiverData)
{
//Code
}
}
}

Au va également pouvoir travailler au niveau des items de notre librairie qui est créée lors de l'utilisation de nos workflow de cette façon :

Remarquez que les méthodes sont les mêmes que lorsque l'on a utilisé le SPItemEventReceiver, et ce par le fait que SPWorkflowLibraryEventReceiver est une classe « Sealed » qui dérive de SPItemEventReceiver.

II - Liaison déclarative de notre Event Receiver avec une liste

Pour lier notre Event Receiver à une liste de manière déclarative, nous allons devoir créer une Feature.

Voici ci-dessous le code de notre fichier Feature.xml

<?xml version="1.0" encoding="utf-8" ?>
<Feature Id="99823340-D80D-4453-AC32-4E6801718D7E" Scope="Web"
Title="Empeche l'ajout/modification/suppression de colonnes sur toutes les listes Calendrier de ce site web"
xmlns="http://schemas.microsoft.com/sharepoint/">
<ElementManifests>
<ElementManifest Location="Elements.xml"/>
</ElementManifests>
</Feature>

Et ci-dessous celui de notre Elements.xml

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Receivers ListTemplateId="106">
<Receiver>
<Name>Ajout</Name>
<Type>FieldAdding</Type>
<Assembly>EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4f93984638d88477</Assembly>
<Class>EventReceivers</Class>
<SequenceNumber>1000</SequenceNumber>
</Receiver>
<Receiver>
<Name>Modification</Name>
<Type>FieldUpdating</Type>
<Assembly>EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4f93984638d88477</Assembly>
<Class>EventReceivers</Class>
<SequenceNumber>1000</SequenceNumber>
</Receiver>
<Receiver>
<Name>Suppression</Name>
<Type>FieldDeleting</Type>
<Assembly>EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4f93984638d88477</Assembly>
<Class>EventReceivers</Class>
<SequenceNumber>1000</SequenceNumber>
</Receiver>
</Receivers>
</Elements>

Si vous souhaitez plus d'informations sur les Features, je vous conseille également la lecture de cet article, qui montre comment les mettre en ouvre.

Comme d'habitude (sous Visual Studio 2008), on va se créer notre fichier install.bat pour déployer notre Feature ; le voici :

@SET TEMPLATEDIR="c:\program files\fichiers communs\microsoft shared\web server extensions\12\Template"
@SET GACUTIL="C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin\gacutil.exe"
%GACUTIL% -u EventReceivers.dll
%GACUTIL% -if bin\debug\EventReceivers.dll
xcopy /e /y Template\* %TEMPLATEDIR%
@SET STSADM="c:\program files\fichiers communs\microsoft shared\web server extensions\12\bin\stsadm"
%STSADM% -o installfeature -filename Coforcert.ListEventReceiver\feature.xml -force
cscript c:\windows\system32\iisapp.vbs /a "SharePoint - 80" /r

Faites attention à bien remplacer "SharePoint - 80" par votre Application Pool, ou si vous ne savez pas lequel c'est, remplacez toute la ligne par un IISRESET (moins performant en terme de durée).

Ensuite attacher votre fichier bat aux commandes Post Build lors de la réussite de la génération.

On peut également afficher la fenêtre de sortie.

. pour visualiser le résultat depuis Visual Studio après avoir compilé.

Maintenant, si nous allons voir les Features d'un de nos sites web, nous aurons ceci :

Si nous activons la fonctionnalité et qu'ensuite nous créons une liste Calendar, puis que nous tentons d'ajouter une colonne par exemple, nous sommes redirigés vers la page d'erreur, qui nous affiche le message d'erreur que vous avons défini dans la propriété ErrorMessage.

Remarquez que j'ai pourtant effectué une opération d'ajout de colonne avec le compte administrateur de la ferme SharePoint.

La méthode réalisée ci-dessus est assez facile à mettre en ouvre quand on a l'habitude de travailler avec les Features, ce qui doit être le cas de tous les développeurs sur la plateforme wss 3 / moss 2007, cependant, elle présente plusieurs inconvénients :

- Dans notre fichier Elements.xml, nous devons ajouter autant de noud Receiver que nous avons de Event Handler, ce qui peut devenir assez rapidement fastidieux si vous implémentez souvent des Event Receivers,

- Cette Feature doit obligatoirement avoir un scope déclaré à Web, et rien d'autre ; ce qui signifie que vous devrez activer la Feature sur chacun des sites web concernés, 1 par 1,

- Le Event Receiver doit obligatoirement être lié à un Template de liste, et donc sera appliqué à toutes les instances découlant de ce Template de liste du site web sur lequel la Feature est activée ; il nous est impossible de spécifier une liste particulière :s

Nous pouvons très facilement faire le test en créant une nouvelle liste « MonCalendrier », basée sur le Template Calendrier. Nous obtenons le message d'erreur ci-dessous :

Heureusement comme toujours, le modèle objet vient à notre secours.

III - Liaison dynamique de notre Event Receiver avec une liste

Une alternative à la méthode déclarative est de passer par le modèle objet de SharePoint.

Cette méthode est de loin la plus puissante, car elle va nous permettre de contrôler liste par liste, les évènements que nous souhaitons capturer.

Voici par exemple le code qui va nous permettre de faire exactement la même chose que précédemment.

string listName = "Calendrier";
SPList currentList = SPContext.Current.Web.Lists[listName];
string ReceiverAssemblyName = "EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4f93984638d88477";
string ReceiverClassName = "Coforcert.Events.EventReceivers.ListEventReceiver";
currentList.EventReceivers.Add(SPEventReceiverType.FieldAdding, ReceiverAssemblyName, ReceiverClassName);
currentList.EventReceivers.Add(SPEventReceiverType.FieldDeleting, ReceiverAssemblyName, ReceiverClassName);
currentList.EventReceivers.Add(SPEventReceiverType.FieldUpdating, ReceiverAssemblyName, ReceiverClassName);

Ici, nous récupérons notre liste, et nous lui ajoutons des évènements dynamiquement depuis le modèle objet. Ce bout de code peut être positionné n' importe où. On pourrait donc par exemple développer une webpart qui se chargerait de lier des évènements prédéfinis à une liste choisie, n'importe où dans notre ferme.

Pour réaliser la même opération que précédemment, à savoir activer notre Event Receiver lors de l'activation d'une Feature, il nous suffit pour cela de créer une Feature, et de rajouter du code dans le handler « FeatureActivated »

public override void FeatureActivated(SPFeatureReceiverProperties properties) {
string listName = "Calendrier";
SPList currentList = SPContext.Current.Web.Lists[listName];
string ReceiverAssemblyName = "EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4f93984638d88477";
string ReceiverClassName = "Coforcert.Events.EventReceivers.ListEventReceiver";
if (!currentList.Equals(null)) {
currentList.EventReceivers.Add(SPEventReceiverType.FieldAdding, ReceiverAssemblyName, ReceiverClassName);
currentList.EventReceivers.Add(SPEventReceiverType.FieldDeleting, ReceiverAssemblyName, ReceiverClassName);
currentList.EventReceivers.Add(SPEventReceiverType.FieldUpdating, ReceiverAssemblyName, ReceiverClassName);
currentList.Update();
}
}

N'oubliez pas bien évidemment de supprimer vos évènements lorsque vous désactiverez la feature ; dans notre cas, nous allons supprimer tous nos Event Handlers. Cependant, gardez bien en mémoire qu'il se peut que quelqu'un d'autre que vous aie rajouté d'autres Event Handlers sur la liste, et donc pensez à rajouter des tests conditionnels appropriés.

Voici le code pour désactiver nos Event Handlers

public override void FeatureDeactivating(SPFeatureReceiverProperties properties){
string listName = "Calendrier";
SPSite curSite = new SPSite("http://wss3/sites/dev");
SPWeb curWeb = curSite.RootWeb;
SPList currentList = curWeb.Lists[listName];
List<SPEventReceiverDefinition> CurrentListEventReceivers = new List<SPEventReceiverDefinition>(currentList.EventReceivers.Count);
if (!currentList.Equals(null)) {
foreach (SPEventReceiverDefinition receiverDef in currentList.EventReceivers)
CurrentListEventReceivers.Add(receiverDef);
foreach (SPEventReceiverDefinition receiver in CurrentListEventReceivers)
receiver.Delete();
}
}

Voila. Une fois cette Feature installée et activée, l'évènement se déclenchera uniquement sur la liste portant le nom « Calendrier » ; toutes les autres listes basées sur le même type, ne seront pas concernées.

Voici le résultat obtenu sur la liste nommée calendrier

Cependant, sur la liste « monCalendrier », cela fonctionne.

IV - Déploiement d'un Event Receiver

Pour activer nos Event Receiver, nous devons déployer notre assembly dans le GAC, et déployer nos fichiers dans le répertoire Features.

C'est ce que nous avons réalisé dans notre fichier install.bat

Pour aller jusqu'au bout des choses, nous allons également créer une solution pour déployer notre Event Receiver et bénéficier d'une gestion centralisée et automatisée de notre composant.

Voici le code du manifest.xml

><Solution SolutionId="FBEA352E-375A-47b7-BAFB-E7CA20F3C383" xmlns="http://schemas.microsoft.com/sharepoint/">
<FeatureManifests>
<FeatureManifest Location="Coforcert.DynamicListEventReceiver\feature.xml" />
<FeatureManifest Location="Coforcert.ListEventReceiver\feature.xml" />
</FeatureManifests>
<Assemblies>
<Assembly DeploymentTarget="GlobalAssemblyCache" Location="EventReceivers.dll"/>
</Assemblies>
</Solution>

Et celui du cab.ddf

;
.OPTION EXPLICIT
.Set CabinetNameTemplate=Coforcert.Events.EventReceivers.wsp
.set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=Package

Solution\manifest.xml manifest.xml
TEMPLATE\FEATURES\Coforcert.DynamicListEventReceiver\feature.xml Coforcert.DynamicListEventReceiver\feature.xml
TEMPLATE\FEATURES\Coforcert.ListEventReceiver\feature.xml Coforcert.ListEventReceiver\feature.xml
TEMPLATE\FEATURES\Coforcert.ListEventReceiver\elements.xml Coforcert.ListEventReceiver\elements.xml
bin\Debug\EventReceivers.dll EventReceivers.dll

Et enfin le fichier deploy.bat

;
makecab /f Solution\cab.ddf

@SET SPDIR="c:\program files\fichiers communs\microsoft shared\web server extensions\12"

%SPDIR%\bin\stsadm -o addsolution -filename PACKAGE\Coforcert.Events.EventReceivers.wsp
%SPDIR%\bin\stsadm -o execadmsvcjobs
%SPDIR%\bin\stsadm -o deploysolution -name Coforcert.Events.EventReceivers.wsp -immediate -allowGacDeployment -force
%SPDIR%\bin\stsadm -o execadmsvcjobs

Si vous souhaitez plus d'informations sur les solutions, je vous conseille cet article.

Vous pouvez également utiliser wspbuilder (merci Stephane ;)) pour générer vos solutions en 15sec.

V - Points Supplémentaires

V-A - Attention aux évènements qui se déclenchent eux-mêmes !

Vous pouvez vous trouver dans une situation ou vous devez par exemple ajouter d'autres éléments lors de l'insertion d'un nouvel élément dans votre liste ; ainsi, vous pouvez donc vous retrouver à écrire le code suivant :

public override void ItemAdding(SPItemEventProperties properties) {
//Code
SPList currentList = SPContext.Current.List;
SPItem item = currentList.Items.Add();
//Code
}

Dans ce cas de figure, vous vous retrouver à ajouter un nouvel élément à votre liste dans la méthode qui intercepte l'ajout d'un élément, ce qui a pour but de réenclencher le handler qui a pour but de rajouter un nouvel élément, et ainsi de suite.

Pour parer facilement à ce genre de situation, il vous suffit d'utiliser les méthodes DisableEventFiring() et EnableEventFiring() de cette façon :

public override void ItemAdding(SPItemEventProperties properties) {
this.DisableEventFiring();
//Code
SPList currentList = SPContext.Current.List;
SPItem item = currentList.Items.Add();
//Code
this.EnableEventFiring();
}

De cette façon, vous désactivez les événements le temps de l'exécution de votre code et les réactivez juste après.

V-B - Workflow et Event Receiver Part 1

Une question qui revient assez fréquemment est le choix entre mettre en ouvre un workflow et mettre en ouvre un Event Receiver.

Bien qu'il n'existe pas de réponse absolue, je pense que la réponse se trouve dans la définition du besoin à satisfaire.

Selon moi, vous devriez mettre en ouvre un workflow si :

- Vous faites intervenir des acteurs humains dans le cadre par exemple de validation,

- Vous avez besoin d'exécuter une action longue,

- Votre action doit prendre en compte les contraintes de la machine ; en effet, même un reboot de la machine ne viendrait pas à bout de votre workflow !

- Vous avez besoin de modéliser un processus.

Et vous devrez mettre en œuvre un item event receiver si :

- Votre action ne fait intervenir aucun acteur extérieur et tout est automatisé,

- Votre action est de courte durée,

- Vous souhaitez exécuter une action particulière, comme annuler toute modification/insertion ou vérifier que le contenu ajouté est valide.

D'autre cas de figure peuvent influencer votre décision, mais je pense que cette (courte) liste est déjà un bon début.

V-C - Workflow et Event Receiver Part 2

En plus de vous poser des problèmes quant au choix de l'un des 2, ils vont également vous poser des problèmes si jamais vous décidez de mettre en ouvre les 2 !

Comme nous l'avons vu, lorsque vous mettez en ouvre des workflows sur une doclib, une nouvelle colonne est rajoutée au niveau de la liste ; or que se passe-t-il si vous avez implémentez un SPListEventReceiver et que vous avez overridé la méthode FieldAdding en empêchant toute modification du schéma de votre liste ?

Vous devriez être redirigé vers une belle page d'erreur ! Or ce n'est pas le cas. Votre workflow cesse tout simplement de s'exécuter, et la colonne correspondant au statut de votre workflow n'apparait pas. Il semblerait donc que le workflow soit désactivé, cependant, si vous regardez les paramètres du workflow de votre liste, ce dernier est bien présent ! Bref, un dysfonctionnement total.

Alors attention à bien planifier vos actions utilisant les workflows et les Event Receiver.

VI - Conclusion

Dans cet article, nous avons vu comment quels types d'Event Receivers nous avions à notre disposition.

Nous avons également vu comment les créer, supprimer et déployer via les Features et Solutions.