Sunday, 3 June 2012

SharePoint 2010 : Event Receivers and their host types


SharePoint gives provision to developers for executing their custom code on many SharePoint actions/ events, e.g. feature activation/ deactivations, List item addition/ deletion etc. These are called as Event Receivers in SharePoint terminology.

Though it seems easy, I feel Event receivers are one of the most complex SharePoint artifacts to understand especially Event Host types.  Hence I decided to do some tutorials. This post talks about my findings as well as some basics.

Following SharePoint 2010 artifacts supports event receivers.
  1. Site collection
  2. Sub-site
  3. Feature
  4. List
  5. List site column
  6. List Item
  7. Workflow

Site Collection (SPSite) Events:

Event Method
Receiver base class
When
Nature
SiteDeleting
SPWebEventReceiver
Occurs when a site collection is being deleted.
Synchronous
SiteDeleted
SPWebEventReceiver
Occurs after a site collection has been deleted.
Synchronous/ Asynchronous

Subsite (SPWeb) Events :

Event Method
Receiver base class
When
Nature
WebAdding
SPWebEventReceiver
Occurs before a new subsite is created
Synchronous
WebProvisioned
SPWebEventReceiver
Fires after a subsite is fully provisioned and the provisioning process has stopped
Synchronous/ Asynchronous
WebDeleting
SPWebEventReceiver
Occurs before an existing Web site is completely deleted.
Synchronous
WebDeleted
SPWebEventReceiver
Occurs after an existing Web site is completely deleted.
Synchronous/ Asynchronous
WebMoving
SPWebEventReceiver
Occurs before an existing Web site has been renamed or moved to a different parent object
Synchronous
WebMoved
SPWebEventReceiver
Occurs after an existing Web site has been moved.
Synchronous/ Asynchronous

Notes:
  1. The Sub Site event receiver does not execute on root web site of the site collection.
  2. Event can be hosted by either individual subsite or whole site collection. It is controlled by scope of the feature who is registering the event and Scope attribute of the Receivers element.
Figure 1 : Receivers Element Scope attribute


List (SPList) Events:

Event Method
Receiver base class
When
Nature
ListAdding
SPListEventReceiver
Occurs before a list is created on a website
Synchronous
ListAdded
SPListEventReceiver
Raised after a list is added to a website
Synchronous/ Asynchronous
ListDeleting
SPListEventReceiver
Raised before a list is deleted.
Synchronous
ListDeleted
SPListEventReceiver
Occurs after a list is deleted.
Synchronous/ Asynchronous
EmailReceived
SPEmailEventReceiver

Provides a method for trapping the event when a list receives an e-mail message
Synchronous/ Asynchronous

Notes:
Event can be hosted by either individual subsite or whole site collection. It is controlled by scope of the feature who is registering the event and Scope attribute of the Receivers element. Please see the figure 1.

List Field (SPField) Events:

Event Method
Receiver base class
When
Nature
FieldAdding
SPListEventReceiver
Occurs before a field link is added to a content type, or field is added to a list.
Synchronous
FieldAdded
SPListEventReceiver
Occurs after a field link is added or after field is added to a list.
Synchronous/ Asynchronous
FieldDeleting
SPListEventReceiver
Occurs before a field is removed from the list
Synchronous
FieldDeleted
SPListEventReceiver
Occurs after a field link has been removed from the content type or field has been removed from the list
Synchronous/ Asynchronous
FieldUpdating
SPListEventReceiver
Occurs before a field link is updated or field is updated in list
Synchronous
FieldUpdated
SPListEventReceiver
Occurs after a field link has been updated or field is updated in list
Synchronous/ Asynchronous

List Item (SPListItem) Events:

Event Method
Receiver base class
When
Nature
ItemAdding
SPItemEventReceiver
Occurs before an item is added
Synchronous
ItemAdded
SPItemEventReceiver
Occurs after an item is added
Synchronous/
Asynchronous
ItemDeleting
SPItemEventReceiver
Occurs before an item is deleted
Synchronous
ItemDeleted
SPItemEventReceiver
Occurs after an item is deleted
Asynchronous
ItemUpdating
SPItemEventReceiver
Occurs before an item is changed
Synchronous
ItemUpdated
SPItemEventReceiver
Occurs after an item is changed
Synchronous/ Asynchronous
ItemFileConverted
SPItemEventReceiver
Occurs after a file in a document library is converted from one type to another
Synchronous/ Asynchronous
ItemFileMoving
SPItemEventReceiver
Occurs before a file is moved
Synchronous
ItemFileMoved
SPItemEventReceiver
Occurs after a file is moved
Synchronous/ Asynchronous
ItemCheckingIn
SPItemEventReceiver
Occurs before an item is checked in
Synchronous
ItemCheckedIn
SPItemEventReceiver
Occurs after an item is checked in
Synchronous/ Asynchronous
ItemCheckingOut
SPItemEventReceiver
Occurs before an item is checked out
Synchronous
ItemCheckedOut
SPItemEventReceiver
Occurs after an item is checked out
Synchronous/ Asynchronous
ItemAttachmentAdding
SPItemEventReceiver
Occurs before an attachment is added to an item
Synchronous
ItemAttachmentAdded
SPItemEventReceiver
Occurs after an attachment is added to an item
Synchronous/ Asynchronous
ItemAttachmentDeleting
SPItemEventReceiver
Occurs before an attachment is removed from an item
Synchronous
ItemAttachmentDeleted
SPItemEventReceiver
Occurs after an attachment is removed from an item
Synchronous/ Asynchronous


Workflow Events:

Event Method
Receiver base class
When
Nature
WorkflowStarting
SPWorkflowEventReceiver
Occurs before a workflow is created
Synchronous
WorkflowStarted
SPWorkflowEventReceiver
Fires once workflow is started
Synchronous/ Asynchronous
WorkflowPostponed
SPWorkflowEventReceiver
Raised when the workflow is postponed, which happens the farm is too busy handling other workflows.
Synchronous/ Asynchronous
WorkflowCompleted
SPWorkflowEventReceiver
Occurs after an existing Web site is completely deleted.
Synchronous/ Asynchronous

Important Notes for List, List field, list item and workflow events:
  1. Event can be hosted by Site Collection, individual subsite, List templates, List instances, Content Type. It is controlled by scope of the feature who is registering the event and Scope attribute of the Receivers element. Please see the figure 1.
  2. Event can be registered to the list using <Receivers> tag by two ways.
    1. Attaching to the list template using  <Receivers ListTemplateId="" >
    2. Attaching to the list instance using <Receivers ListUrl="" >
  3. Events can be registered to the content type by copying the Receiver tag into the XmlDocuments tag. Event receivers can be registered to both site content type (element.xml) or list content type (list schema.xml)

Feature Events:

Event Method
Receiver base class
When
Nature
FeatureActivated
SPFeatureReceiver
Raised after a feature has been activated
Asynchronous
FeatureDeactivating
SPFeatureReceiver
Raised before a feature is deactivated
Synchronous
FeatureInstalled
SPFeatureReceiver
Raised after a feature has been installed
Asynchronous
FeatureUninstalling
SPFeatureReceiver
Raised before a feature is uninstalled
Synchronous
FeatureUpgrading
SPFeatureReceiver
Raised when a feature is upgrading
Synchronous

Notes:
Nothing explains Feature Upgrade event receiver better than Chris O’Brien’s post http://www.sharepointnutsandbolts.com/2010/06/feature-upgrade-part-1-fundamentals.html.

Ways to Attach Event Receivers:
  • Declarative Way (element.xml)
    In this approach, event receivers are registered using element manifest files. This approach works in Sandbox solution as well.
  • Programmatic way
    In this way, event receivers are created programmatically, ideally in feature receiver code.

You can download my tutorial having many examples of all kind of event receivers with different host types here.

Below, I have explained the tutorial code with help of screenshots taken using SharePoint Manager 2010. For testing purposes, I created a Site Collection named as SharePoint Custom Development and a sub-site named as SubSite.

1.   SPSiteEventReceivers
Shows how to create site collection event receivers.


<!--The Project item should be associated with a feature scoped at site collection.-->
  <Receivers >
      <Receiver>
        <Name>SPSiteEventReceiversSiteDeleting</Name>
        <Type>SiteDeleting</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.SPSiteEventReceivers.SPSiteEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>SPSiteEventReceiversSiteDeleted</Name>
        <Type>SiteDeleted</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.SPSiteEventReceivers.SPSiteEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
  </Receivers>

2.    SiteCollectionScopedSPWebEventReceivers:
These are the sub-site event receivers hosted by site collection.

  
<!-- The parent feature scope is 'Site'. The host of these events will be site collection due to 
  Receivers tag Scope='Site' attribute. 
  Hence these events will be bound to every subsite within the site collection except RootWeb-->
  <Receivers Scope="Site" >
      <Receiver>
        <Name>SiteCollectionScopedSPWebEventReceiversWebDeleting</Name>
        <Type>WebDeleting</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.SiteCollectionScopedSPWebEventReceivers.SiteCollectionScopedSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>SiteCollectionScopedSPWebEventReceiversWebMoving</Name>
        <Type>WebMoving</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.SiteCollectionScopedSPWebEventReceivers.SiteCollectionScopedSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>SiteCollectionScopedSPWebEventReceiversWebAdding</Name>
        <Type>WebAdding</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.SiteCollectionScopedSPWebEventReceivers.SiteCollectionScopedSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
  </Receivers>

  • Host type of both above event receivers is Site Collection. Hence when seen in the Event Receiver collection of Site collection, it look like this.

Figure 2 : Site collection and sub-site events hosted by Site Collection



3.    IndividualSubSiteSPWebEventReceivers
These sub site event receivers are hosted by individual sub site.

  <!--
  These events will be bound to the subsites in which parent feature is activated because.
   1. The parent feature scope is 'Web'. 
   2. Scope='Web' attribute of Receivers tag. 
  -->
  
  <Receivers Scope="Web" >
      <Receiver>
        <Name>IndividualSubSiteSPWebEventReceiversWebDeleted</Name>
        <Type>WebDeleted</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.IndividualSubSiteSPWebEventReceivers.IndividualSubSiteSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>IndividualSubSiteSPWebEventReceiversWebMoved</Name>
        <Type>WebMoved</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.IndividualSubSiteSPWebEventReceivers.IndividualSubSiteSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
      <Receiver>
        <Name>IndividualSubSiteSPWebEventReceiversWebProvisioned</Name>
        <Type>WebProvisioned</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.IndividualSubSiteSPWebEventReceivers.IndividualSubSiteSPWebEventReceivers</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
  </Receivers>

Figure 3 : Sub-site Event hosted by sub site


4.    ListTemplateEventReceiver
  • This event receiver is associated with Announcement List Template. All list created through Announcement list template will have this event receiver attached.


  <!--
 ListTemplateId attribute is used to attach the ItemAdded event to Announcement list template.
  As the parent feature scope 'Web', the host type of this event will be individual subsite in which 
  parent feature is activated.
   -->
  <Receivers ListTemplateId="104">
      <Receiver>
        <Name>ListTemplateEventReceiverItemAdded</Name>
        <Type>ItemAdded</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.ListTemplateEventReceiver.ListTemplateEventReceiver</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
  </Receivers>


Figure 4 : Parent feature plays role in deciding the Host


5.    IndividualListEventReceiver
This event receiver is attached to the Test Events List created declaratively.


  <!--
ListUrl attribute is used to attach the ItemAdded event to "Test Events List" list created in this project.-->
  <Receivers ListUrl="Lists/Test Events List">
      <Receiver>
        <Name>IndividualListEventReceiverItemAdded</Name>
        <Type>ItemAdded</Type>
        <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
        <Class>EventReceivers.IndividualListEventReceiver.IndividualListEventReceiver</Class>
        <SequenceNumber>10000</SequenceNumber>
      </Receiver>
  </Receivers>


6.    ContentTypeEventReceiver
This shows how to associate event receiver to a content type.


<!--
Parent ContentType: Announcement (0x0104). Note that without "Inherits='false'",
event receiver does not get attached to the content type. This is due to a known bug in SharePoint 2010. -->
  <ContentType ID="0x010400069AB2346B6241699E565C8E004F2F4E"
               Name="Custom Announcement"
               Group="Custom Content Types"
               Description="This CT is created to demonstrate the association of event receivers."
               Inherits="false"
               Overwrite="TRUE"
               Version="0">
    <FieldRefs>
              <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title"/>
    </FieldRefs>
    <XmlDocuments>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/events">
        <Receivers xmlns:spe="http://schemas.microsoft.com/sharepoint/events">
          <Receiver>
            <Name>ContentTypeEventReceiver</Name>
            <Type>ItemAdded</Type>
            <SequenceNumber>10000</SequenceNumber>
            <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
            <Class>$SharePoint.Type.a3dac829-4c36-46a9-930c-55698372b050.FullName$</Class>
            <Data></Data>
            <Filter></Filter>
          </Receiver>
        </Receivers>
      </XmlDocument>
    </XmlDocuments>
  </ContentType>


Figure 5 : Attach event receiver to content type

7.    WorkflowEventReceivers
  • This example shows how to attach the workflow event receivers to list template and individual list.


<!--Workflow events will be attached to the Links list template.
VS 2010 asks for selecting the list template id by default.
But by changing the ListUrl attribute, events can be attached to individual list too.
-->
<Receivers ListTemplateId="103">
       <Receiver>
              <Name>WorkflowEventReceiversWorkflowStarting</Name>
              <Type>WorkflowStarting</Type>
              <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
              <Class>EventReceivers.WorkflowEventReceivers.WorkflowEventReceivers</Class>
              <SequenceNumber>10000</SequenceNumber>
       </Receiver>
</Receivers>
<Receivers ListUrl="Lists/Links">
       <Receiver>
              <Name>WorkflowEventReceiversWorkflowCompleted</Name>
              <Type>WorkflowCompleted</Type>
              <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
              <Class>EventReceivers.WorkflowEventReceivers.WorkflowEventReceivers</Class>
              <SequenceNumber>10000</SequenceNumber>
       </Receiver>
</Receivers>



Figure 6 : Workflow event receivers


8.    Programmatically creating Event receivers :
The below code shows how to register/ remove the event receiver through feature receiver .


public class EventReceiverAssociatorEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWeb web = properties.Feature.Parent as SPWeb;
            if (web != null)
            {
                SPList list = web.Lists.TryGetList("Object Model Event Receivers");

                if (list == null)
                {
                    Guid listGUID = web.Lists.Add("Object Model Event Receivers",
                                                  "Created for testing programmatic association of event receivers.",
                                                  SPListTemplateType.GenericList);
                    list = web.Lists[listGUID];
                }

                AddEventReceiverToList("EventReceivers.ProgrammaticallyAddedEventReceiver.ProgrammaticallyAddedEventReceiver",
                                        list,
                                        "EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f3155a6cff2154bb",
                                        SPEventReceiverType.ItemAdded,
                                        SPEventReceiverSynchronization.Asynchronous);
            }
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWeb web = properties.Feature.Parent as SPWeb;
            if (web != null)
            {
                SPList list = web.Lists.TryGetList("Object Model Event Receivers");

                if (list != null)
                {
                    foreach (SPEventReceiverDefinition eventreceiverDef in list.EventReceivers)
                    {
                        if (eventreceiverDef.Assembly == "EventReceivers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f3155a6cff2154bb"
                            && eventreceiverDef.Type == SPEventReceiverType.ItemAdded)
                        {
                            eventreceiverDef.Delete();
                            break;
                        }
                    }
                }
            }
        }

        protected static void AddEventReceiverToList(string className, SPList list, string assemblyName, SPEventReceiverType eventReceiverType, SPEventReceiverSynchronization eventReceiverSynchronization)
        {
            if (className == null) throw new ArgumentNullException("Class name cannot be null.");
            if (list == null) throw new ArgumentNullException("List cannot be null.");
            if (assemblyName == null) throw new ArgumentNullException("Assembly name cannot be null.");
            SPEventReceiverDefinition eventReceiver = list.EventReceivers.Add();
            eventReceiver.Class = className;
            eventReceiver.Assembly = assemblyName;
            eventReceiver.Type = eventReceiverType;
            eventReceiver.Synchronization = eventReceiverSynchronization;
            eventReceiver.Update();
        }
    }




Updated (11/7/2012):
While using BIN deployment in one of my projects, I observed that the feature with event receivers throws File Not Found error and does not get activated. After googling, I came to know that this behavior is by design. SharePoint 2010 does not support Event receivers. I also observed that event receiver feature gets activated when I added Unrestricted="true" to the NamedPermissionSet into the custom CAS Policy config file present in the SharePointRoot\CONFIG folder. Unfortunately this change is not recommended due to security reasons. So only way to fix this is to change the Deployment target to GAC instead of BIN.

Few Key take ways and findings:
  1. Feature scope plays role in deciding the event receiver host object
  2. Host type also can be controlled through Receivers tag Scope attribute .
  3. Sandbox solution does not allow event receivers to be attached programmatically.
  4. Declaratively event receivers can be attached to individual list using ListUrl attribute of Receivers tag.