Il blog di Giuseppe Marchi - SharePoint MVP
NAVIGATION - SEARCH

Gli event handler di WSS 3.0

Gli event handler di WSS 3.0
Data: 12/06/2007
Categoria: Sharepoint
Codice d'esempio


Gli event handler sono una delle feature più utili per quanto riguarda la personalizzazione delle funzionalità di Windows Sharepoint Services. In parole povere, tramite questi meccanismi, è possibile eseguire del codice inserito in un assembly .NET allo scatenarsi della maggior parte delle operazioni effettuate su un oggetto Sharepoint; queste operazioni vanno dai semplici inserimenti, modifiche e cancellazioni, per arrivare al check-in/check-out dei documenti, all'upload di un file e alla cancellazione/creazione di siti o sottositi.
Gli event handler erano gia presenti nella versione precedente del prodotto (la 2.0), ma erano solamente applicabili alle document library; ora, nella versione 3.0 di WSS, il supporto per questi meccanismi è stato aggiunto a tutti i tipi di liste presenti nella configurazione di default di Sharepoint, a singoli file, a siti e alle feature (una delle novità della nuova versione del prodotto). Inoltre, i concetti su cui si basavano prima gli event handler, sono stati notevolmente rinnovati, attraverso l'introduzione di nuove caratteristiche. Vediamole in dettaglio:
  • è possibile gestire eventi per singoli file, liste, siti e features.
  • Sono supportati tutti i tipi di liste, sia quelli di default, che quelli custom creati da noi.
  • È possibile registrare più eventi per un singolo oggetto.
  • Possiamo usufruire di eventi sincroni (before-events) ed eventi asincroni (after-events).
  • E' possibile stoppare l'esecuzione di un evento sincrono e stampare a video dei messaggi di errore personalizzati.
  • E' possibile recuperare, oltre alle modifiche al contenuto di particolari oggetti, anche al loro schema (per esempio: aggiunte o modifiche ai campi di una lista).
Sharepoint stesso, fa un grandissimo utilizzo dei suoi event handlers. Basti vedere, per esempio, il meccanismo con cui viene fatto partire un workflow: alla creazione, possiamo scegliere se far partire il flusso all'inserimento di un nuovo item in una lista (evento ItemAdded) o alla modifica di un item gia esistente all'interno della lista (evento ItemUpdated).

Il nostro primo event handler

Come primo esempio, cadiamo nel banale e creiamo il classico Hello World. Creiamo quindi, un nuovo progetto in Visual Studio .NET 2005, scegliamo il tipo "Class Library" e il nostro linguaggio preferito (C# va benissimo ;) e proseguiamo con la creazione.
Una volta creato il progetto, dobbiamo prima effettuare due operazioni fondamentali:
  • inserimento della reference alla libreria Microsoft.Sharepoint.dll
  • Creazione di una chiave e sign dell'assembly con uno strong name, in modo tale da poterlo inserire all’interno della GAC del server Sharepoint.
La prima operazione ci serve per abilitare l'intellisense all'interno del nostro progetto Visual Studio, per quanto riguarda tutte le classi facenti parte del modello ad oggetti di Sharepoint.
La seconda, invece, è fondamentale per la corretta registrazione dell'event handler, poiché l'assembly relativo deve essere inserito nella GAC (Global Assembly Cache) del server per poter essere registrato e funzionare in modo adeguato. Per effettuare questa operazione, basta seguire queste istruzioni:
  • fare click con il tasto destro sul progetto e selezionare la voce "Properties",
  • andare nell tab "Signing",
  • scegliere l’opzione di sign dell'assembly,
  • creare una nuova chiave selezionando la voce "New" nella relativa combo,
  • scrivere il nome delle vostre chiavi (può essere uno qualsiasi),
  • completare la procedura facendo click su OK.
Oppure, seguire la procedura a mano riportata in questa pagina della documentazione.

Fatto questo, aggiungiamo una nuova classe alla nostra soluzione ed ereditiamola dalla classe SPItemEventReceiver, classe base per gli event handler da applicare alle liste Sharepoint. Tale classe verrà poi istanziata da Sharepoint ogni volta che viene effettuata un'operazione sulla relativa lista. Se poi, all'interno della nostra classe, abbiamo scritto il codice per l'overload di qualche metodo che gestisce qualche evento della lista, questo verrà eseguito dal sistema, altrimenti le operazioni lanciate sulla lista proseguiranno normalmente.
Nell'esempio, scriviamo la frase "Hello event handler world !" nel campo Title di ogni elemento che viene aggiunto all'interno della lista in cui abbiamo registrato il nostro handler.

namespace Peppe.Sharepoint.Handlers

{
public class HelloEventHandler : SPItemEventReceiver
{
public override void ItemAdding(SPItemEventProperties properties)
{
properties.AfterProperties["Title"] = "Hello event handler world !";
}
}
}

L'utilizzo della proprietà AfterProperties della classe SPItemEventProperties, di cui disponiamo un'istanza in ogni evento gestito dal nostro handler, ci permette di recuperare o settare dei valori per tutti i campi dell'elemento che viene aggiunto, in quanto questo ancora non è presente nel database dei contenuti e quindi, l'equivalente proprietà ListItem, che viene invece utilizzata in quasi tutti gli after-event, risulta ancora nulla.
Ricapitolando, le AfterProperties sono valorizzate in tutti gli eventi di ogni tipo di lista, mentre la proprietà ListItem, che rappresenta l'elemento sul quale è stato scatenato l'evento, è disponibile (con i valori aggiornati) solamente negli after-event, escluso l’evento ItemDeleted (per ovvi motivi).

Registrare un event handler

Ok. Abbiamo appena creato il nostro primo event handler. Ora dobbiamo registrarlo e attaccarlo ad una lista di un sito Sharepoint.
Per la registrazione dell'handler, possiamo avvalerci dell'utilizzo di una feature (uno dei nuovi meccanismi di Sharepoint Services 3.0, per questa implementazione rimandiamo alla lettura di questo tutorial), mentre per allegarlo ad un oggetto dobbiamo obbligatoriamente scrivere del codice .NET per comporre una windows form o, semplicemente, una console application che effettui tale operazione tramite il modello ad oggetti di Sharepoint.
Vediamo, quindi, l'implementazione di un'applicazione console che registra l'event handler d'esempio che abbiamo visto precedentemente:

namespace AddHandler

{
class Program
{
static void Main(string[] args)
{
string siteURL = ConfigurationManager.AppSettings["siteURL"];
string listName = ConfigurationManager.AppSettings["listName"];

using (SPSite site = new SPSite(siteURL))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[listName];
list.EventReceivers.Add(SPEventReceiverType.ItemAdding,
"Peppe.Sharepoint.Handlers, Version=1.0.0.0,
Culture=neutral,
PublicKeyToken=78fde53655a71179"
,
"Peppe.Sharepoint.Handlers.HelloEventHandler");
list.Update();

Console.WriteLine("Handlers inseriti correttamente !");
}
}

Console.WriteLine("Premere un tasto per uscire ...");
Console.Read();
}
}
}


Nota: io, per semplice comodità, sono solito aggiungere un secondo progetto alla mia soluzione e creare una console application come questa appena vista, che effettui la registrazione dell’handler. E' però anche disponibile un tool gratuito per evitare questo lavoro: Event Handler Explorer (direttamente dal blog di Patrick Tisseghem MVP - U2U).
Inoltre, la scelta di registrare l'handler via codice, è stata inserita come nuova feature nella versione 3.0 di Sharepoint (di cui vedremo le altre novità nel prossimo capitolo). Restano comunque funzionanti le proprietà EventSinkAssembly, EventSinkClass e EventSinkData via interfaccia web.

Prima però di lanciare questa console application dal server dove è installato Sharepoint, dobbiamo inserire l’assembly contenente il nostro handler nella Global Assembly Cache (GAC) del server stesso. Per farlo, possiamo andare su Control Panel > Administration Tools > Microsoft .NET 2.0 Configuration > Manage the assembly cache > Add an assembly to the assembly cache, e selezionare la nostra dll; oppure potete creare un file .bat contenente queste istruzioni:

gacutil /U Peppe.Sharepoint.Handlers

gacutil /i Peppe.Sharepoint.Handlers.dll
iisreset

che non fanno nient'altro che rimuovere l'assembly dalla GAC (se gia installato), installarlo di nuovo (in caso ci fosse una nuova versione) ed infine effettuare un reset del web server IIS.

Nota: Il reset è necessario per avvisare Sharepoint della presenza di una nuova versione del nostro assembly all'interno della GAC.

Ora, l'handler è collegato alla nostra lista. Creando un nuovo elemento, sarà possibile vederne il corretto funzionamento.

Hello Event Handler World
Figura 1 - Hello event handler world !

Le novità della versione 3.0

Nell'esempio abbiamo implementato la gestione dell'evento ItemAdding. Come gia detto però, abbiamo la possibilità, grazie alla nuova versione di Sharepoint, di gestire un'insieme veramente variegato di eventi di una lista, in modo tale da implementare specifiche azioni personalizzate a fronte di specifiche operazioni effettuate su ogni lista. Questo l’elenco degli eventi disponibili:
  • ItemAdded – Aggiunta di un elemento (after-event)
  • ItemAdding - Aggiunta di un elemento (before-event)
  • ItemAttachmentAdded - Aggiunta di un allegato (after-event)
  • ItemAttachmentAdding - Aggiunta di un allegato (before-event)
  • ItemAttachmentDeleted – Cancellazione di un allegato (after-event)
  • ItemAttachmentDeleting - Cancellazione di un allegato (before-event)
  • ItemCheckedIn – Check-in del documento (after-event)
  • ItemCheckedOut – Check-out del documento (after-event)
  • ItemCheckingIn - Check-in del documento (before-event)
  • ItemCheckingOut - Check-out del documento (before-event)
  • ItemDeleted – Cancellazione di un documento (after-event)
  • ItemDeleting - Cancellazione di un documento (before-event)
  • ItemFileConverted – Conversione di un file (after-event)
  • ItemFileMoved – Spostamento di un file (after-event)
  • ItemFileMoving – Spostamento di un file (before-event)
  • ItemUncheckedOut – Unchek-out di un documento (after-event)
  • ItemUncheckingOut – Unchek-out di un documento (before-event)
  • ItemUpdated – Aggiornamento di un item (after-event)
  • ItemUpdating – Aggiornamento di un item (before-event)
Come potete vedere, nell'elenco di tutti gli eventi di una lista generica di Sharepoint, possiamo decidere di gestirne di sincroni o di asincroni; questo ci permette di eseguire delle operazioni che con la vecchia versione non potevamo fare, poiché non era implementata la parte sincrona degli event handler, ma solo quella asincrona.
Possiamo, per esempio, decidere di creare un handler che stoppi una qualsiasi operazione su tutti gli item di una lista (per esempio la cancellazione) e che proponga all’utente un messaggio di errore:

namespace Peppe.Sharepoint.Handlers

{
public class CancelEventHandler : SPItemEventReceiver
{
public override void ItemDeleting(SPItemEventProperties properties)
{
properties.Cancel = true;
properties.ErrorMessage = "Gli elementi di questa lista " +
"non possono essere cancellati !";
}
}
}

Questo tipo di operazione, che prima non era possibile, ci viene offerta dalla gestione dell'evento ItemDeleting, uno dei nuovi eventi sincroni aggiunti in questa versione del prodotto.

Cancel Event Handler
Figura 2 - Cancel event handler

Non solo liste ...

Assieme alla gestione di tutti gli eventi di qualsiasi tipo di lista Sharepoint, il meccanismo degli event handler, ha allargato il suo raggio d'azione in modo tale da comprendere gli eventi di intere site collection, singoli siti, di singoli file, di singole features e degli elementi dello schema delle liste, sia presenti di default che personalizzate.
E' ora possibile quindi, ereditando dalla classe SPWebEventReceiver, eseguire delle operazioni personalizzata alla cancellazione o allo spostamento di un singolo sito Sharepoint o di un’intera site collection:

namespace Peppe.Sharepoint.Handlers

{
public class WebEventHandler : SPWebEventReceiver
{
public override void SiteDeleting(SPWebEventProperties properties)
{
properties.Cancel = true;
properties.ErrorMessage = "Non puoi cancellare una site collection";
}

public override void WebMoving(SPWebEventProperties properties)
{
if (properties.Web.Title == "Administration")
{
properties.Cancel = true;
properties.ErrorMessage = "Questo sito non può essere spostato";
}
}
}
}

In questo caso però, il codice per la registrazione dell'handler risulta un po' diverso:

SPEventReceiverDefinitionCollection webReceivers = web.EventReceivers;

SPEventReceiverDefinition webDef = webReceivers.Add();
webDef.Name = "WebEventHandler";
webDef.Assembly = "Peppe.Sharepoint.Handlers, Version=1.0.0.0, Culture=neutral, " +
"PublicKeyToken=78fde53655a71179";
webDef.Class = "Peppe.Sharepoint.Handlers.WebEventHandler";
webDef.Type = SPEventReceiverType.WebMoving;
webDef.SequenceNumber = 10020;
webDef.Update();

SPEventReceiverDefinitionCollection siteReceivers = site.RootWeb.EventReceivers;
SPEventReceiverDefinition siteDef = siteReceivers.Add();
siteDef.Name = "WebEventHandler";
siteDef.Assembly = "Peppe.Sharepoint.Handlers, Version=1.0.0.0, Culture=neutral, " +
"PublicKeyToken=78fde53655a71179";
siteDef.Class = "Peppe.Sharepoint.Handlers.WebEventHandler";
siteDef.Type = SPEventReceiverType.SiteDeleting;
siteDef.SequenceNumber = 10020;
siteDef.Update();

Oppure, ereditando dalla classe SPFeatureReceiver, è possibile eseguire del codice .NET allo scatenarsi di una delle seguenti azioni di una particolare feature Sharepoint:
  • Installazione
  • Disinstallazione
  • Attivazione
  • Disattivazione

Nell'esempio, all'attivazione della feature, viene creata una lista custom; mentre alla sua disattivazione, tale lista viene cancellata.

namespace Peppe.Sharepoint.Handlers

{
public class FeatureEventHandler : SPFeatureReceiver
{
private const string listName = "NomeLista";

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
using (SPSite site = properties.Feature.Parent as SPSite)
{
using (SPWeb web = site.OpenWeb())
{
web.Lists.Add(listName, "", web.ListTemplates["Document Library"]);
SPList list = web.Lists[listName];
list.OnQuickLaunch = true;
list.Update();
}
}
}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
using (SPSite site = properties.Feature.Parent as SPSite)
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[listName];
list.Delete();
}
}
}

public override void FeatureInstalled(SPFeatureReceiverProperties properties)
{}

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
{}
}
}

In questo caso, la registrazione di questo tipo di handler, avviene direttamente all'interno della definizione della feature stessa, attraverso le proprietà ReceiverAssembly e ReceiverClass:

<?xml version="1.0" encoding="utf-8"?>

<Feature Id="0F2B103C-4B25-4dc1-B8C6-96FE08792ABC"
Title="PeppeDotNet event handlers feature"
Description=""
Scope="Site"
Hidden="FALSE"
xmlns="http://schemas.microsoft.com/sharepoint/"
ReceiverAssembly="Peppe.Sharepoint.Handlers, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=78fde53655a71179"

ReceiverClass="Peppe.Sharepoint.Handlers.FeatureEventHandler"
>
</Feature>


Feature Event Handler
Figura 3 - Feature event handler

Per l'installazione di una feature, rimandiamo la lettura di questi due tutorial: "How to: Create a Simple Feature" e "Installing or Uninstalling Features", in quanto non risulta inerente all’argomento in questione.

Conclusioni
In questa nuova versione di Windows Sharepoint Services, il meccanismo sottostante gli event handler è stato resto (finalmente) davvero potente. Oltre ad aver aggiunto il supporto per tutti i tipi di liste (sia di default che custom), possiamo gestire eventi relativi alla maggior parte degli oggetti di Sharepoint, come singoli file, singoli siti, intere site collection o feature.
Inoltre, è stata aggiunta la gestione degli eventi sincroni, il che ci permette di allargare ancora di più il numero di personalizzazioni fattibili tramite gli event handler, che nelle versioni precedenti erano implementati solamente per le document library.
Ora, non vi resta che provare. Vedrete che ne rimarrete entusiasti !

Link utili
SPListEventReceiver class
SPItemEventReceiver class
SPWebEventReceiver class
SPFeatureReceiver class
GacUtil tool