Events in SharePoint 2007
Code download available at:OfficeSpace2007_11.exe(167 KB)
Event Receiver Classes
Before Events and After Events
Validating Before Events
After Event Handlers
Binding Handlers to Events
Using the WSS Object Model
Windows® SharePoint® Services (WSS) 3.0 offers developers many significant improvements for building custom business solutions based on SharePoint sites. One of the biggest developer-focused enhancements has to do with a new infrastructure for handling server-side events. For example, when a user performs an action that modifies content in a SharePoint site—say, uploading a new document or deleting an item from a list—there is an opportunity for a developer to respond to this user action with an event handler that executes server-side logic written in C# or Visual Basic®.
While WSS 2.0 also provided an infrastructure for server-side event handling, things are vastly improved in WSS 3.0. Figure 1 shows an overview of the differences between the event-handling support in WSS 2.0 versus WSS 3.0. Furthermore, it's important for you to understand that the new WSS 3.0 event-handling infrastructure is not a mere refinement of what was already there in the previous version. The new WSS 3.0 event-handling infrastructure represents a complete replacement for the WSS 2.0 event-handling infrastructure. One of the biggest changes is that event handling is implemented at the list level and not the document library level. That means events that fire on list definitions and list items work on all types of lists, including document libraries.
Figure 1 Event-Handling in WSS 2.0 and WSS 3.0
|Event Type||WSS 2.0 Infrastructure||WSS 3.0 Infrastructure|
|Events on document libraries||Supported||Supported|
|Multiple event handlers bound to a single event||Not Supported||Supported|
|Events on lists||Not Supported||Supported|
|Events on list definitions||Not Supported||Supported|
|Events on sites||Not Supported||Supported|
|Before-the-fact, synchronous event handlers with cancellation||Not Supported||Supported|
|After-the-fact, asynchronous event handlers||Supported||Supported|
|Event handler classes||Must implement the IListEventSink interface||Must inherit from receiver base classes in WSS object model|
The older WSS 2.0 infrastructure has a completely different programming model and requires a different approach for wiring event handlers to events. WSS 3.0 provides support for both the new version 3.0 event-handling infrastructure as well as the older infrastructure from WSS 2.0. You should understand, however, that the support in WSS 3.0 for the older version 2.0-style of event handlers has only been included to provide backward compatibility when integrating code written for WSS 2.0 solutions into a WSS 3.0 environment. When you begin developing solutions for WSS 3.0 and Microsoft® Office SharePoint Server (MOSS) 2007, you should employ the new WSS 3.0 event-handling infrastructure, which is based on new concepts and the programming model that I am going to introduce in this month's Office Space column.
Event Receiver Classes
Event handling in WSS 3.0 is based on event receiver classes. More specifically, you create a new event receiver class by inheriting from one of several event receiver base classes that are provided as public classes within the WSS object model. To do this, you should create a new class library project that references the core WSS assembly named Microsoft.SharePoint.dll.
The list of the WSS receiver base classes available through the WSS object model is shown in Figure 2. Once you create a class definition that inherits from one of these WSS receiver base classes, you add event handlers by overriding selected methods.
Figure 2 WSS Event Receiver Base Classes
|SPListEventReceiver||Provides event handling when users add, change, and remove columns for a list definition.|
|SPItemEventReceiver||Provides event handling when users modify items within a list or when users modify documents within a document library.|
|SPWebEventReceiver||Provides event handling when users move or delete a site collection or a site.|
|SPEmailEventReceiver||Provides event handling when users send e-mail to an e-mail- enabled list.|
Once you have implemented a custom receiver class with one or more event handlers, you must then compile it into an assembly with a strong name and install it in the global assembly cache (GAC). You can then bind your event handlers to events exposed by a host object such as a site, a list, or a document library. As you will see later in this column, you have two choices when it comes to binding event handlers. You can bind event handlers using a Feature with declarative XML syntax or you can bind them programmatically using the WSS object model. Once you have bound an event-handler method to a specific event, WSS then takes care of executing your handler in response to the user actions that trigger the event.
Before Events and After Events
WSS 3.0 events can be separated into two main categories: Before events and After events. Before events fire in response to a user's action at a time before WSS has written any data back to the content database. A key point is that Before events occur early enough in the request processing lifecycle that they support the cancellation of the user's action. Therefore, Before events provide a great opportunity to perform security checks and custom validation.
WSS also supports After events, whose event handlers execute after WSS 3.0 has written back to the content database to commit the user's action. After events do not support canceling the user's action. Instead, After events are used to kick off custom processing logic designed to run whenever content has been modified.
The event handler for a Before event runs on the same thread that is processing the user's action (such as adding an item). Therefore, Before events have a blocking nature and are commonly referred to as synchronous events. WSS executes the event handlers for After events on a separate thread so their processing does not block the response that is sent back to the user. For this reason, After events are commonly referred to as asynchronous events. Notice also that Before events are based on overridable methods whose names end with "ing" (for example, FieldDeleting), while After event method names end with "ed" (for example, FieldDeleted).
Let's look at an example of authoring a new custom receiver class that defines an event handler for a Before event and for an After event. Figure 3 shows a custom receiver class that inherits from the SPListEventReceiver class and overrides the FieldDeleting and FieldDeleted methods. You should take note that when WSS calls one of these event handlers, it passes a single parameter of type SPListEventProperties named properties.
Figure 3 Custom Receiver Class
Imports Microsoft.SharePoint Public Class MyListEvents Inherits SPListEventReceiver Public Overrides Sub FieldDeleting ( _ ByVal properties As SPListEventProperties) '*** your custom event handling code goes here End Sub Public Overrides Sub FieldDeleted ( _ ByVal properties As SPListEventProperties) '*** your custom event handling code goes here End Sub End Class
Since the FieldDeleting method handles a Before event, it supports canceling the user's action. Imagine a scenario where you want to prohibit all users including the site collection administrator from being able to delete columns from a particular list definition. You can accomplish this by assigning a value of True to the Cancel property of the SPListEventProperties parameter named properties. When canceling the user's action, you should also assign a custom error message by assigning a string value to the ErrorMessage property. This error message will be shown directly to the user:
Public Overrides Sub FieldDeleting( _ ByVal properties As SPListEventProperties) '*** cancel user action properties.ErrorMessage = "Deleting not supported!" properties.Cancel = True End Sub
When an event handler for a Before event assigns a value of True to the Cancel property, WSS responds by short-circuiting the request and canceling the user's action. Instead of deleting the column as the user requested, WSS displays the custom error message to the user. The key point here is that Before events provide you with a layer of defense against unwanted modifications.
Now that you have seen the basics of canceling the user's action in the event handler for a Before event, let's create another event handler for a Before event with some conditional logic. Imagine you have a requirement where you need to run a security check to ensure users meet certain criteria before allowing them to delete items from a list or to delete documents from a document library. Have a look at the ItemDeleting event handler in the receiver class definition shown in Figure 4. Notice that the conditional logic cancels the user's action when it determines the user does not possess the permissions of a site administrator.
Figure 4 Simple Security Check
Public Class MyItemEvents Inherits SPItemEventReceiver Public Overrides Sub ItemDeleting( _ ByVal properties As SPItemEventProperties) If (Not properties.OpenWeb().CurrentUser.IsSiteAdmin()) Then Dim ErrorMessage As String ErrorMessage = "Only site administrator can delete items" properties.ErrorMessage = ErrorMessage properties.Cancel = True End If End Sub End Class
As you can see, this event-handler logic requires that the current user has certain permissions before permitting a deletion to occur. Taking this example one step further, you can write an event handler to determine whether users can delete items depending on whether they are members of an Active Directory® group. Since WSS is built on top of the ASP.NET runtime, you can accomplish this with a simple ASP.NET programming technique and query the IsInRole method exposed by the User property of the HttpContext object:
If (Not HttpContext.Current.User.IsInRole( _ "MyDomain\ContentManagers")) Then Dim ErrorMessage As String ErrorMessage = _ "Only members of ContentManagers can delete items" properties.ErrorMessage = ErrorMessage properties.Cancel = True End If
Validating Before Events
Now let's move on and examine the possibilities of writing validation logic in an event handler for a Before event. This makes it possible to write event handlers that make users conform to application-specific constraints when they add or modify items within a list. For example, you can validate that the ship date for an invoice is not a Saturday or Sunday or that a phone number or e-mail address meets a pattern defined by a regular expression.
One of the most powerful aspects of writing validation logic for Before events is that you can perform a validation that spans columns. In the sample code for this month, I use a Sales Leads list that contains a column for a work phone number and a column for an e-mail address. I have an event handler that will perform a validation on each item with the Sales Leads list to require that either the work phone number or the e-mail address has a non-blank value. The basic idea here is that sales leads are not valid if someone has not entered in any contact information.
There are two ways for a user to modify a list item. A user can add a new item or update an existing item. When writing validation logic for Before events, you must typically add redundant code behind the event handlers bound to the ItemAdding event and the ItemUpdating event. Therefore, it's best to maintain the validation logic in a separate method that can be called from two different event handlers. The SalesLeadIsValid method shown in Figure 5 accepts two string parameters named Phone and E-mail and returns True if either of these parameters is not blank.
Figure 5 Validating Input
Private Function SalesLeadIsValid( ByVal Phone As String, ByVal Email As String) _ As Boolean '*** make sure we have either phone or e-mail If (String.IsNullOrEmpty(Phone) And _ String.IsNullOrEmpty(Email)) Then Return False Else Return True End If End Function
With the validation logic inside the SalesLeadIsValid method, you can call this method from event handlers bound to the ItemAdding event as well as the ItemUpdating event. Note that within an event handler for an ItemAdding event or a ItemUpdating event, you must retrieve the column values against which you want to perform validations. The SPItemEventProperties parameter exposes two collections named BeforeProperties and AfterProperties. The idea is that you can retrieve the value of any column in a state either before or after the user changed it.
Figure 6 shows an example of implementing an ItemAdding event handler that retrieves the After values for both the Phone and E-mail and passes them to the SalesLeadIsValid method to determine whether to allow the user's action to succeed.
Figure 6 Validating Against an Action
Public Overrides Sub ItemAdding( _ ByVal properties As SPItemEventProperties) Dim Phone As String, Email As String Phone = _ CType(properties.AfterProperties("WorkPhone"), _ String) Email = _ CType(properties.AfterProperties("EMail"), _ String) If (Not SalesLeadIsValid(Phone, Email)) Then Dim ErrorMessage As String ErrorMessage = _ "Sales lead must have valid phone or email" properties.ErrorMessage = ErrorMessage properties.Cancel = True End If End Sub
Take a closer look at how the event handler retrieves the two column values from the AfterProperties collection. AfterProperties is a collection with a default parameterized property (an indexer) that accepts the name of a column. Note that the parameter passed to AfterProperties is a case-sensitive string value. You should also understand that this parameter represents the physical name of the column as opposed to the display name.
After Event Handlers
An After event is different from a Before event because it fires after an action has been completed and it does not provide the developer with an opportunity to cancel the action. After events provide the developer with an opportunity to kick off custom processing that should execute when the user has successfully completed an action that has modified content within a SharePoint site. For example, once a user has added a new item to a list, an After event can be used to reformat one of the columns to maintain data integrity or to send out a notification e-mail message to a specific user or distribution list.
Let's look at an example of writing an event handler for our Sales Lead list that will be bound to the ItemAdded event. In this example, there is a requirement to format all sales lead names with all capital letters. An event handler takes the column value with the name of the sales lead and reformats it using uppercase text.
Note that you have to be careful when you write an event handler for an After event and you modify the item on which the event is based. The problem is that modifying the item will cause the event to fire again. When the event fires again it wants to modify the item, starting a recursive loop. To avoid this you must disable the event firing on the current item while you are modifying it. This is done by calling the DisableEventFiring method supplied by the underlying base class. After you have modified the item, you should then call EnableEventFiring. Here's how all the pieces fit together:
Public Overrides Sub ItemUpdated( _ ByVal properties As SPItemEventProperties) Me.DisableEventFiring() Dim SalesLead As String = _ CType(properties.ListItem("Title"), String) '*** reformat item column values as required properties.ListItem("Title") = SalesLead.ToUpper() properties.ListItem.Update() Me.EnableEventFiring() End Sub
Now you have seen several different examples of writing event handlers for both Before events and After events. You should now be more comfortable with why and how to write event handlers. The main question that remains is how to bind them to events on host object within a SharePoint site, so let's look at that now.
Binding Handlers to Events
Earlier I mentioned that receiver classes must be compiled into an assembly DLL with a strong name and installed in the GAC. Your options for binding event handlers are to use declarative XML in a Feature or programmatically bind the two via the WSS object model. However, both approaches still require that you know the complete four-part assembly name, as well as the namespace-qualified name, of the receiver class containing the event handlers to which you want to bind.
I will begin by discussing how to bind event handlers to a list type using declarative XML within a Feature. You can accomplish this by adding a Receivers element with one or more inner Receiver elements. In the sample code that accompanies this column, there is a Feature with a Receivers element that binds two different event handlers to the WSS Contacts list type (see Figure 7). The Contacts list has a list type identifier of 105. This means that any list that is created from the Contacts list inside a site that has this Feature activated will automatically have these event handler bindings.
Figure 7 Contacts List Receiver
<Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <!-- Receivers element used only in feature where Scope=Web --> <Receivers ListTemplateId="105" > <Receiver> <Name>Field_Deleting_Event</Name> <Type>FieldDeleting</Type> <Assembly>OfficeSpaceEventDemo, [4-part assembly name]</Assembly> <Class>OfficeSpaceEventDemo.MyListEvents</Class> <SequenceNumber>1000</SequenceNumber> <Data>My Custom Data</Data> </Receiver> <Receiver> <Name>Field_Deleted_Event</Name> <Type>FieldDeleted</Type> <Assembly>OfficeSpaceEventDemo, [4-part assembly name]</Assembly> <Class>OfficeSpaceEventDemo.MyListEvents</Class> <SequenceNumber>1000</SequenceNumber> <Data>My Custom Data</Data> </Receiver> </Receivers> </Elements>
Note that inside the Receivers element you must supply a separate Receiver element for each event handler you want to bind. There is no supported technique to bind multiple event handlers at once with a single Receiver element.
There are two mandatory pieces of binding information in a Receiver element. They are the Assembly element and the Class element, which must contain the complete four-part assembly name and namespace-qualified receiver class name respectively. The Name element within a Receiver element can be used to assign a friendly name to the event handler for tracking purposes, but is not required.
The SequenceNumber element within a Receiver element is useful when you have multiple event handlers bound to a single event. The event handler with the lower sequence number always fires before an event handler with a higher sequence number. Therefore, you can add in multiple event handlers and be guaranteed a deterministic execution order.
The Data element within a Receiver element allows you to configure a particular event handler binding with any custom string data you would like. You can access this string value from within the Data element using the ReceiverData property of the SPListEventProperties parameter and use it in an application-specific manner to provide numbers or regular expressions for validation routines or custom error messages:
Public Overrides Sub FieldDeleting( _ ByVal properties As SPListEventProperties) Dim BindingData As String BindingData = properties.ReceiverData '*** use binding data in application-specific fashion End Sub
Using the WSS Object Model
While binding event handlers to events declaratively using a Receivers element in a Feature works in some scenarios, it lacks the flexibility you need in many others. For example, the Receivers element can only be used to bind an event handler to a list type on a site-wide basis. This declarative approach does not provide a way to bind an event handler to a single list instance in a site without also binding it to every other list of the same type.
When designing custom SharePoint solutions that utilize event handlers, you will find that binding event handlers with code provides much more flexibility. For example, you can bind an event handler to an event just for a single list.
Let's look at an example of binding the event handler for the ItemAdding event to the Sales Leads list:
Dim SalesLeadsList As SPList = Me.Web.Lists("Sales Leads") '*** bind event handler to ItemAdding event SalesLeadsList.EventReceivers.Add( _ SPEventReceiverType.ItemAdding, _ GetType(MyItemEvents).Assembly.FullName, _ GetType(MyItemEvents).FullName)
Each SPList object exposes an EventReceivers collection. The EventReceivers collection exposes an Add method with three different overloaded implementations. The Add method in this last example represents the simplest approach, which requires passing three parameters. When you want to bind an event handler through code, you must minimally supply the name of the assembly, the namespace-qualified class name, and the event type using the SPEventReceiverType enumeration from the WSS object model.
Note that while you can embed literal string values inside your code containing the four-part assembly name and namespace-qualified receiver class name, you can also use the code shown earlier to discover these values dynamically.
Calling the Add method represents the easiest approach, but it is not flexible enough for all scenarios. Figure 8 illustrates a more complicated approach in which the event-handler binding is performed by programming directly against a SPEventReceiverDefinition object. This gives you an opportunity to give the binding a specific GUID identifier so you can add and remove them more easily. It also gives you the ability to initialize the SequenceNumber property and the Data property.
Figure 8 Binding Against a GUID
Dim SalesLeadsList As SPList = Me.Web.Lists("Sales Leads") Dim receivers As SPEventReceiverDefinitionCollection receivers = SalesLeadsList.EventReceivers Dim ItemAddingDefId As Guid ItemAddingDefId = New Guid("D6C452E5-95D4-45e8-934C-30F785867000") If (Not receivers.EventReceiverDefinitionExist(ItemAddingDefId)) Then Dim ItemAddingDef As SPEventReceiverDefinition ItemAddingDef = receivers.Add(ItemAddingDefId) ItemAddingDef.Name = "ItemAddingHandler" ItemAddingDef.Type = SPEventReceiverType.ItemAdding ItemAddingDef.Assembly = GetType(MyItemEvents).Assembly.FullName ItemAddingDef.Class = GetType(MyItemEvents).FullName ItemAddingDef.Data = "My Custom Data" ItemAddingDef.SequenceNumber = 1001 ItemAddingDef.Update() End If
I have really just scratched the surface of what is possible when binding event handlers through the WSS object model. I demonstrated working with the EventReceivers collection of a list, but you should know that several other types in the WSS object model have an EventReceivers collection and can act as event hosts. Examples of such types include sites, content types, and files.
Send your questions and comments for Ted to firstname.lastname@example.org.
Ted Pattison is an author, trainer, and SharePoint MVP who lives in Tampa, Florida. Ted has just completed his book titled Inside Windows SharePoint Services 3.0 for Microsoft Press. He also delivers advanced SharePoint training to professional developers through his company, Ted Pattison Group (www.TedPattison.net).