Monday, May 28, 2012

Working with BeforeProperties and AfterProperties on SPItemEventReceiver

As many of you know, event receivers are a great way to hook into various SharePoint events.  These can apply to Feature events such as FeatureActivated, List events such as FieldAdded, and many others.  The most common set of receivers used, however, are part of SPItemEventReceiver which let you wire your code up to a number of events that can occur to items on a list or library.
When working with events, you’ll quickly find that before (synchronous) and after (asynchronous) events exist, and the method suffix such as “ing” (e.g. ItemAdding) and “ed” (e.g. ItemAdded) will tell you whether it gets invoked before or after the actual change is made.  Basic stuff.
And, as you get deeper, you’ll even find that you can extract the before and after state of the change.  For example, you can hook into the ItemUpdating event for a document library and prevent a user from changing a certain column.  The code might look like this:
public override void  ItemUpdating(SPItemEventProperties properties)
{
     if (properties.BeforeProperties["column"] != properties.AfterProperties["column"])
    {
        properties.Cancel = true;
        properties.ErrorMessage = "This column cannot be changed";
    }
}
For a document library, this works just fine.  However, you should know that the BeforeProperties hash table is not populated for items on a list.  As is worded in the SDK: “For documents, Before and After properties are guaranteed for post events, such as ItemUpdated, but Before properties are not available for post events on list items”
When they say “not available for post events on list items”, do they mean after events (like ItemUpdated, ItemDeleted, etc)?  The wording is curious here, so I thought I’d take some time to test each combination of common events such as Add, Update and Delete.  These were done across a custom list and then a document library.  Each test involved adding a new item, editing the item and then deleting the item.  Here are the results for a list:
List BeforeProperties AfterProperties properties.ListItem
ItemAdding No value New value Null
ItemAdded No value New value New value
ItemUpdating No value Changed value Original value
ItemUpdated No value Changed value Changed value
ItemDeleting No value No value Original value
ItemDeleted No value No value Null
No value means that column value in the hash table was not available. 
New value means that the correct value for the column was available. 
Changed value means that the correct updated value was available.
Original value means that the correct original value was available.
Here is the same test against a document library:
Library BeforeProperties AfterProperties properties.ListItem
ItemAdding No value No value Null
ItemAdded No value No value New value
ItemUpdating Original value Changed value Original value
ItemUpdated Original value Changed value Changed value
ItemDeleting No value No value Original value
ItemDeleted No value No value Null

Properties.ListItem refers the the current value for the list item at that point in the event.  Null means that the item is not available.  My analysis yields the following results:
  • Not surprisingly, we get null values for for ItemAdding (before item is added) and ItemDeleted (after item is deleted).  This was proven by Ishai Sagi some time ago.
  • As correctly documented in the SDK, item events for lists do not expose BeforeProperties.
  • ItemAdding and ItemAdded correctly report the value in the AfterProperties for an list item, but not a library item.  This is curious.
  • We have no visibility on the previous states during the ItemDeleted event.  Once it’s deleted, it’s clearly gone.

So, if we go back to our original problem listed above.  How can we prevent a user from changing a certain column for an item in a list event?  From the list table, you can see if we hook into the ItemUpdating event, we can compare the current item’s value (properties.ListItem) to the AfterProperties value.  The code would look like this:
if (properties.ListItem["column"] != properties.AfterProperties["column"])
{
    properties.Cancel = true;
    properties.ErrorMessage = "This column cannot be changed";
} 
 
Original Post : http://www.synergyonline.com/Blog/Lists/Posts/Post.aspx?ID=122 
These are the values of the properties in List events:

List BeforeProperties AfterProperties properties.ListItem
ItemAdding No Value No Value Null
ItemAdded No Value No Value New Value
ItemUpdating Original Value Changed Value Original Value
ItemUpdated Original Value Changed Value Changed Value
ItemDeleting No Value No Value Original Value
ItemDeleted No Value No Value Null

And here are the properties available in Library events:

Library BeforeProperties AfterProperties properties.ListItem
ItemAdding No Value No Value Null
ItemAdded No Value No Value New Value
ItemUpdating Original Value Changed Value Original Value
ItemUpdated Original Value Changed Value Changed Value
ItemDeleting No Value No Value Original Value
ItemDeleted No Value No Value Null
 
 Hope this helps!

Event Receiver enhancements in SharePoint 2010

With SharePoint 2010, there are some good enhancements in Event Receivers.
  1. New Events
  2. New Registration technique
  3. Synchronous After-Events
  4. Custom error pages and redirection
  5.  Impersonation enhancements (SPEventPropertiesBase.OriginatingUserToken)
List of Events : http://msdn.microsoft.com/en-us/library/ms437502.aspx
 
Lets discuss one by one.

1. New Events

As part of SharePoint 2010, there are six new events that you can take advantage of. These events allow you to capture creation and provisioning of new webs and the creation and deletion of lists.
 Event Name
 Event Description
 Event Receiver Class
WebAdding
A synchronous event that happens before the web is added. Some URL properties may not exist yet for the new site, since the new site does not exist yet.
SPWebEventReceiver
WebProvisioned
A synchronous or asynchronous after-event that occurs after the web is created. You make the event synchronous or asynchronous by using the Synchronization property and setting it to synchronous or Synchronous. This is located under the Receiver node in the elements.xml file for your feature.
SPWebEventReceiver
ListAdding
A synchronous event that happens before a list is created.
SPListEventReceiver
ListAdded
A synchronous or asynchronous after-event that happens after a list is
created but before being it is presented to the user.
SPListEventReceiver
ListDeleting
A synchronous event that happens before a list is deleted.
SPListEventReceiver
ListDeleted
A synchronous or asynchronous after-event that happens after a list is
deleted.
SPListEventReceiver


2. New Event Registration technique using the <Receivers/> tag

SharePoint 2010 has added a new registration mechanism for registering your event receivers, using the <Receivers> XML block.
<Receivers
  ListTemplateId = "Text"
  ListTemplateOwner = "Text"
  ListUrl = string
  RootWebOnly = TRUE | FALSE
  Scope = Site | Web>
</Receivers>
With this new capability, you can register your event receiver at the site collection level by using the new Scope attribute and setting it either to  Site or Web, depending on the scope that you want for your event receiver.
  • SPSite-Level Binding
In SharePoint 2010, you have the ability to deploy an event receiver not only at the web level, but also at the Site Collection level. In fact, all of the event receivers except the SPEmailEventReceiver can be deployed at the Site Collection level, so you can share their behavior across all the websites within that Site Collection. For instance, you can share at the Site Collection level, a list-level event receiver that checks list provisioning and/or custom fields using a unique deployment step.
  • Event Binding by List Template
Also, In SharePoint 2010 we can bind an event receiver to a list template, using  <ListTemplateId>  and then apply that event receiver to every list instance based on that ListTemplateId.
Below example displays a feature element that installs an event receiver for a list.
<?xml version=”1.0” encoding=”utf-8”?>
<Elements xmlns=”http://schemas.microsoft.com/sharepoint/”>
<Receivers ListTemplateId=”104”>
<Receiver>
<Name>News Update Event Receiver</Name>
<Type>ItemUpdating</Type>
<Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
<Class>AmitKumawat.SP2010.EventReceivers.NewsItemUpdateReceiver</Class>
<Data>Contains a string that is used to pass parameters to the receiver via the ReceiverData property</Data>
<SequenceNumber>1002</SequenceNumber>
</Receiver>
</Receivers>
</Elements>
The <Data> tag of the Receiver element allows you to define a custom configuration text that will be provided to the event receiver via the ReceiverData property of the SPEventPropertiesBase base class, whenever the receiver is invoked. Note that the length of the content inside Data element is limited to 255 chars.
Another important one is the ListUrl attribute, which allows you to scope your receiver to a particular list by passing in the relative URL.

Note: ListUrl works only if feature has Scope=”Web”. In case the feature has Scope=”Site”, event receiver is fired for every list, ListUrl is ignored.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Receivers ListUrl="Lists/Tasks">
    <Receiver>
      <Assembly>MyReceiverAssembly, Version=1.0.0.0, Culture=Neutral,
        PublicKeyToken=12e5e5525fb3d28a</Assembly>
      <Class>MyReceiverAssembly.MyReceiverClass</Class>
      <Type>ItemAdded</Type>
      <Name>My ItemAdded Event Receiver</Name>
      <Synchronization>Synchronous</Synchronization>
      <SequenceNumber>1000</SequenceNumber>
    </Receiver>
  </Receivers>
</Elements>
You can tell SharePoint to just have the receiver work on the root site by using the RootWebOnly attribute on the <Receivers> node.

3.  Synchronous After-Events

We now have  support for synchronous after – events, such as listadded , itemadded , or webprovisioned .
In SharePoint, all the Before events are executed synchronously within the same process and thread of the current user request, while all After events are executed asynchronously, on a background thread and potentially in a process different from the current user request.

Important Bullet Points:
  • Synchronous event receivers are called in sequential order based on the sequence number specified during event binding. This applies to both Before and After synchronous events.
  • Asynchronous After event receiver threads are initiated in sequential order based on the sequence number. However, there is no guarantee that they will finish in that same order.
  • An asynchronous After event can start at any time after its associated user action is performed. It may start before, at the same time as, or after the Web request is completed.
To make the After events, such as ItemUpdated, synchronous, you need to set the Synchronization property either through the SPEventReceiverDefinition object model if you are registering your events programmatically or by creating a node in your <Receiver> XML that sets the value to Synchronous or Asynchronous.
The property can take one of the following values:
  • Default : Before events are synchronous; After events are asynchronous.
  • Synchronous : The current event is executed synchronously.
  • Asynchronous The current event is executed asynchronously.
To configure this property, you can define it in the feature element XML file or in Code
<?xml version=”1.0” encoding=”utf-8”?>
 <Elements xmlns=”http://schemas.microsoft.com/sharepoint/”>
 <Receivers ListTemplateId=”105”>
<Receiver>
 <Name>News Update Event Receiver</Name>
 <Type>ItemUpdating</Type>
 <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
 <Class>AmitKumawat.SP2010.EventReceivers.NewsItemUpdateReceiver</Class>
 <Data>Contains a string that is used to pass parameters to the receiver via the ReceiverData property</Data>
 <SequenceNumber>1002</SequenceNumber>
 <Synchronization>Synchronous</Synchronization>
 </Receiver>
</Receivers>
 </Elements>


Another option is to configure the Synchronization property in code, as shown below:

using (SPSite site = new SPSite(“http://sp2010site/”)) 
{
using (SPWeb web = site.OpenWeb()) 
{
NewsItemEventReceiverConfiguration config = new NewsItemEventReceiverConfiguration();

SPList list = web.Lists[“Contacts”];
var newReceiver = list.EventReceivers.Add();
Assembly asm = Assembly.LoadFrom(
@”..\..\AmitKumawat.SP2010.EventReceivers.dll”);
newReceiver.Assembly = asm.FullName;
newReceiver.Class = asm.GetType(
“AmitKumawat.SP2010.EventReceivers.NewsItemEventReceiver”).FullName;
newReceiver.Name = “News Receiver”;
newReceiver.Type = SPEventReceiverType.ItemUpdated;
newReceiver.SequenceNumber = 110;
newReceiver.Data = XmlSerializationUtility.Serialize<NewsItemEventReceiverConfiguration>(config);
newReceiver.Synchronization =SPEventReceiverSynchronization.Synchronous;
newReceiver.Update();
}
}

4. Custom Error Pages

In Sharepoint 2007, you can cancel events and return an error message to the user,which provides limited interactivity and is not much helpful to the user beyond what the error message says.
In Sharepoint 2010 , you can cancel the event  and redirect the user to a custom error page that you create. This allows you to have more control of what the users see, and you can try to help them figure out why their action is failing.
However, Please note that the custom error pages and redirection will only work for  synchronous Before-Events(ending with ..ing), so you cannot achieve this for  synchronous After-Events such as ListAdded . Also, this will only work when the client is a Browser.
The way to implement custom error pages is to set the Status property on your property bag for your event receiver to SPEventReceiverStatus.CancelWithRedirectUrl , set the RedirectUrl property to a relative URL for your error page, and set the Cancel property to true .
 properties.Cancel = true;
 properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
 properties.RedirectUrl = "/_layouts/mycustomerror.aspx";

5.  Impersonation enhancements.

By default, all events in SharePoint run under the context of the user who raised the event.
 Generally, this is okay, but there may be certain times when you want to let a user perform actions on lists or libraries that the current user does not have permissions to do. In most cases, you would use SPSecurity.RunwithElevatedPrivileges method. However, you may want to revert to the originating user on some operations.
With 2010, the event property bag contains the OriginatingUserToken ,UserDisplayName , and UserLoginName, which you can use to revert to the original user.
Below is an example  that perform some tasks by using the OriginatingUserToken to impersonate the originating user.
public override void ItemUpdated(SPItemEventProperties properties) {
using (SPSite site = new SPSite(properties.SiteId,
properties.OriginatingUserToken)) {
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl)) {
// Some tasks here impersonating the originating user token
}
}
base.ItemUpdated(properties);
}