Using Event Receivers in SharePoint Foundation 2010 (Part 2 of 2)

Summary:  Event receivers in Microsoft SharePoint Foundation 2010 enable your custom code to respond when specific actions occur on a SharePoint object. Practical examples in this article show you how to use events to enhance your SharePoint applications.

Applies to: Business Connectivity Services | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio

Provided by:  Ali Badereddin, Microsoft Corporation | Nick Gattuccio, Microsoft Corporation

Contents

  • Practical Examples of Using Events

  • Example 1: Broadcasting Announcements

  • Example 2: Splitting Items

  • Example 3: Tracking Document Sources

  • Example 4: Canceling and Redirecting

  • Example 5: Logging Events

  • Conclusion

  • Additional Resources

This article is a continuation of Using Event Receivers in SharePoint Foundation 2010 (Part 1 of 2).

Practical Examples of Using Events

Now that you have a solid understanding of the event model in Microsoft SharePoint Foundation 2010, you can begin building events handling into your own code. The remainder of this article presents five examples that use the SharePoint event model in various ways:

  • Broadcasting Announcements   Announcements added to any of hundreds of sites in a site collection all appear in the Announcements Web Part on the root site.

  • Splitting Items   When an item is added to a list, the item is automatically split into two or more items. This example also demonstrates canceling an event with no error and using property bags in a Before event receiver.

  • Tracking Document Sources   When a document in a document library is edited, a metadata property is updated. This example demonstrates property promotion and property demotion.

  • Canceling and Redirecting   When a user operation is canceled, the user is redirected to an error page.

  • Logging Events   Each event that occurs in a site collection is logged as a list item in a list that is stored on the site.

For each of these samples, you can download a Microsoft Visual Studio solution:

To help you navigate the samples, we provide the following overview of the SharePoint event model. Table 3 lists all SharePoint Foundation 2010 event receivers by scope and specifies the classes to which they belong and the event hosts with which they can associate.

Table 3. Event receivers grouped by scope

Scope

Methods

Class

Hosts

Site collection

  • SiteDeleting

  • SiteDeleted

SPWebEventReceiver

  • Site collection

Web

  • WebAdding

  • WebProvisioned

  • WebDeleting

  • WebDeleted

  • WebMoving

  • WebMoved

SPWebEventReceiver

  • Site collection web

List

  • ListAdding

  • ListAdded

  • ListDeleting

  • ListDeleted

SPListEventReceiver

  • Site collection web

  • List templates

List

  • EmailReceived

SPEmailEventReceiver

  • Site collection

  • Web

  • List templates

  • List instances

Field

  • FieldAdding

  • FieldAdded

  • FieldUpdating

  • FieldUpdated

  • FieldDeleting

  • FieldDeleted

SPListEventReceiver

  • Site collection

  • Web

  • List templates

  • List instances

Item

  • ItemAdding

  • ItemAdded

  • ItemUpdating

  • ItemUpdated

  • ItemDeleting

  • ItemDeleted

  • ItemCheckingIn

  • ItemCheckedIn

  • ItemCheckingOut

  • ItemCheckedOut

  • ItemFileMoving

  • ItemFileMoved

  • ItemFileConverted

  • ItemAttachmentAdding

  • ItemAttachmentAdded

  • ItemAttachmentDeleting

  • ItemAttachmentDeleted

SPItemEventReceiver

  • Site collection

  • Web

  • List templates

  • List instances

  • Content type

Workflow

  • WorkflowStarting

  • WorkflowStarted

  • WorkflowCompleted

  • WorkflowPostponed

SPWorkflowEventReceiver

  • Site collection

  • Web

  • List templates

  • List instances

  • Content type

Example 1: Broadcasting Announcements

In this example, you have a site collection that contains hundreds of individual sites; on the root site, you have an Announcements Web Part. The goal in this scenario is to have all announcements that are posted on any of the individual sites appear in the Announcements Web Part that you placed on the root site of the site collection.

Click to get codeDownload the code sample: SharePoint 2010: Using Event Receivers, Example 1: Broadcast Announcements

Design

Assuming that the root site of the site collection has an Announcements list, the first thing we do is add the Web Part for this list to the home page of the root site.

To add an Announcements Web Part

  1. Navigate to the home page of the root site of the site collection.

  2. Click the Page tab, and then click Edit Page.

  3. Click the Insert tab, and then click Existing List. You see all the lists and document libraries on the root site.

  4. Select the Announcements list and click Add.

Next, you need to copy all of the announcements that have been posted on any of the subsites to the Announcements list on the root site. To do this, crate an ItemAdded event receiver that copies any item that has been added to a list (based on the Announcements list template) to the Announcements list on the root site. Your event logic should be triggered on every announcement list that is on any site in the site collection (with the exception of the Announcements list of the root site).

Implementation

In Microsoft Visual Studio 2010, create a project based on the SharePoint 2010 Event Receiver project template. Choose to deploy your solution in the sandbox. In the SharePoint Customization Wizard, select the Announcements list as the event source and ItemAdded as the event to override, as shown in Figure 6.

Figure 6. SharePoint Customization Wizard

SharePoint Customization Wizard

This action creates a new Feature at the web scope that has a <Receivers> element that binds the ItemAdded event receiver to the Announcements list template (ListTemplateId="104"), as shown in the following excerpt.

<Receivers ListTemplateId="104">
  <Receiver>
    <Name>EventReceiver1ItemAdded</Name>
    <Type>ItemAdded</Type>
    <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
    <Class>BroadcastAnn.EventReceiver1.EventReceiver1</Class>
    <SequenceNumber>10000</SequenceNumber>
  </Receiver>
</Receivers>

Table 4 explains the various elements within the <Receiver> element.

Table 4. Elements of the Receiver element

Element

Description

Value in this example

Name

The name of the event receiver binding.

EventReceiver1ItemAdded. Feel free to provide your own name.

Type

The type of the event, which can be a text representation of any of the values of the SPEventType enumeration.

ItemAdded.

Assembly

The full assembly name of the DLL that contains the event receiver code.

$SharePoint.Project.AssemblyFullName$. This name is converted to the full assembly name when Visual Studio 2010 builds the solution.

Class

The namespace and class (in the format namespace.class) that inherits from SPEventReceiverBase and contains the event receiver code.

BroadcastAnn.EventReceiver1.EventReceiver1, where "BroadcastAnn.EventReceiver1" is the namespace and "EventReceiver1" is the class name.

SequenceNumber

The order of execution for the event. For example, if two ItemAdding events are bound to the same event host, the one with the lower sequence number triggers first.

10000. That's basically a default value.

Synchronization

Whether the event is synchronous or asynchronous. After events are asynchronous.

Because we did not specify the synchronization and ItemAdded is an After event, the default mode is asynchronous.

To cause the event to trigger on all sites in the site collection, the scope of the Feature must be changed to Site; this way, the functionality is enabled when we activate the Feature at the site collection rather than having to activate the Feature on every subsite. Note that the Site scope of a Feature refers to site collection (see Figure 7); Web scope refers to the sites.

Figure 7. Setting Feature scope

Setting Feature scope

Next, you must open the file, EventReceiver1.cs, and add code to the ItemAdded event. The result is taking the item that was added to the Announcements list on the subweb and copying it to the Announcements list on the root web. The code also appends to the title the name of the person who made the announcement and appends to the body the URL of the subweb where the announcement was made.

public override void ItemAdded(SPItemEventProperties properties)
{
    // Get a reference to the site collection.
    SPSite site = properties.OpenSite();

    // Get a reference to the current site.
    SPWeb currentWeb = properties.OpenWeb();

    // Get a reference to the root site.
    SPWeb rootWeb = site.RootWeb;

    // Skip if the root web is the same as the current web.
    if (rootWeb.Url == currentWeb.Url)
    {
        return;
    }

    // Get the current list.
    SPList currentList = properties.List;

    // Get the announcement list on the root site.
    SPList rootList = rootWeb.Lists["Announcements"];

    // Get the list item that was added.
    SPListItem currentListItem = properties.ListItem;

    // Add the announcement item to the list on the root web.
    SPListItem rootListItem = rootList.Items.Add();
    foreach (SPField field in currentList.Fields)
    {
        if (!field.ReadOnlyField)
        {
            rootListItem[field.Id] = currentListItem[field.Id];
        }
    }

    // Append the user display name to the title.
    rootListItem["Title"] += " - " + properties.UserDisplayName;

    // Append the web URL to the body.
    rootListItem["Body"] += string.Format("This announcements was made by {0} on subweb {1}",
      properties.UserLoginName, properties.WebUrl);

    rootListItem.Update();
}

You can now build the solution and start the debugger. This process creates your solution, deploys it to the SharePoint site collection, and starts the browser on the home page of your site collection. Be sure that your site collection has the Announcements list.

Finally, create a new subsite based on the Team site template. Add an item to the Announcements list and confirm that a copy of the item is shown on the Announcements list on the root site, as shown in Figure 8.

Figure 8. Item added to Announcements list on root site

Item added to Announcements list on root site

Example 2: Splitting Items

In this example, the objective is to add an item to a list and have that item split into two new items. For example, when a user adds an Xbox 360 bundle, you might want to prevent that item from being added and instead add two other items to the list: "Xbox 360" and "Kinect." An even better example is a scenario in which you upload a .zip file and then have the contents of the file extracted with each of its files added as a separate item.

To support the item-splitting scenario, this example also demonstrates how to cancel an action with no error on a Before event receiver.

Click to get codeDownload the code sample: SharePoint 2010: Using Event Receivers, Example 2: Splitting Items

Design

This project needs three content types to represent the three items, "Xbox 360 Bundle", "Xbox 360" and "Kinect," respectively. In this scenario, we need only the titles of the items, so each of the content types inherit from the "Items" content type without adding any extra fields.

When a new item based on the "Xbox 360 Bundle" content type is added, we want to either delete that item or else cancel adding it, and then create two other items based on the "Xbox 360" and "Kinect" content types.

To do this, we must first bind the ItemAdding Before event to the "Xbox 360 Bundle" content type. The ItemAdding event creates the two other items and cancels the addition of the current item. Because we don't want any logic triggered when an "Xbox 360" or "Kinect" item is added, we don't bind those content types to any event receivers. To keep things simple, we are not handling updates; that is, when the content type of an item changes to either "Xbox 360 Bundle" or "Kinect" from "Xbox 360."

On canceling the addition of the "Xbox 360 Bundle" content type, we do not want to receive an error message. Because we access the item that is being added in a Before event (before the changes are committed to the content database), the properties.ListItem property is still null. To access the item fields, we need to use the properties.AfterProperties property bag; this allows us to access the data that we want to copy (just the title in this example), based on the internal names of the fields in the list schema.

Implementation

Create a Visual Studio 2010 project based on the Empty SharePoint Project template. Choose to deploy your solution in sandbox. Right-click the project, and click Add New Item. Select Content Type, name the item Xbox360Bundle, and click Add. Visual Studio creates the content type with the name "projectname - Xbox360Bundle".

Important

Be sure to change the name to simply Xbox360Bundle in the Element.xml file.

Repeat the steps for the other two content types, applying the names Xbox360 and Kinect.

Next, right-click the project and add a new Event Receiver item with the name BundleEventReceiver. Select An Item is being added and click Finish. This step creates an event receiver class that overrides the ItemAdding event. However, by default it also binds the event to a list template. We want to bind the event to the Xbox360Bundle content type instead of the list template. Therefore, we must delete the event binding Elements.xml (shown as selected in Figure 9) and add the binding inside the Xbox360Bundle content type Element.xml.

Figure 9. Elements.xml binding

Elements.xml binding

Before we add the event receiver binding, verify that the Inherits attribute of the content type in Elements.xml is set to FALSE instead of TRUE; otherwise the event receiver binding on the content type will not work. Because we are no longer inheriting, we need to define all the field references ourselves. Thus, we must add a field ref to the Title field. The Xbox360Bundle content type Element.xml should resemble the following.

<Elements xmlns="https://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x01004497ac28eb9a47fbabee43f48c3f5973"
               Name="Xbox360Bundle"
               Group="Custom Content Types"
               Description="My Content Type"
               Inherits="FALSE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" 
                Name="Title" 
                Required="TRUE" 
                ShowInNewForm="TRUE" 
                ShowInEditForm="TRUE"/>
    </FieldRefs>
    <XmlDocuments>
      <XmlDocument NamespaceURI="https://schemas.microsoft.com/sharepoint/events">
        <spe:Receivers xmlns:spe="https://schemas.microsoft.com/sharepoint/events">
          <Receiver>
            <Name>Item Adding Event</Name>
            <Type>ItemAdding</Type>
            <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
            <Class>ItemSplitting.BundleEventReceiver.BundleEventReceiver</Class>
            <SequenceNumber>10000</SequenceNumber>
          </Receiver>
        </spe:Receivers>
      </XmlDocument>
    </XmlDocuments>
  </ContentType>
</Elements>

Now, let's turn to the file BundleEventReceiver.cs and add code to the ItemAdding event that cancels the addition of the Xbox360Bundle item and creates two new items based on the Xbox360 and Kinect content types.

public override void ItemAdding(SPItemEventProperties properties)
{
    // Get a reference to the current list.
    SPList list = properties.List;

    // Get the "Xbox360" content type.
    SPContentType xboxContentType = list.ContentTypes["XBox360"];

    // Get the "Kinect" content type.
    SPContentType kinectContentType = list.ContentTypes["Kinect"];

    // If any of the content types are null, they were not created.
    if (xboxContentType == null || kinectContentType == null)
    {
        properties.Status = SPEventReceiverStatus.CancelWithError;
        properties.ErrorMessage = "The Xbox360 and Kinect content types must be present.";
        return;
    }

    // Disable event firing so that ItemAdding is not called recursively.
    this.EventFiringEnabled = false;

    // Create the "Xbox360" item.
    SPListItem xboxItem = list.AddItem();
    xboxItem["Title"] = properties.AfterProperties["Title"] + " (Xbox 360)";
    xboxItem["ContentTypeId"] = xboxContentType.Id;
    xboxItem.Update();

    // Create the "Kinect" item.
    SPListItem kinectItem = list.AddItem();
    kinectItem["Title"] = properties.AfterProperties["Title"] + " (Kinect)";
    kinectItem["ContentTypeId"] = kinectContentType.Id;
    kinectItem.Update();

    // Re-enable event firing.
    this.EventFiringEnabled = true;

    // Cancel the creation of the "Xbox360Bundle" item but don't throw an error.
    properties.Status = SPEventReceiverStatus.CancelNoError;
}

Notice the following important details about the preceding code:

  • If any of the content types are not bound to the list, we cancel the ItemAdding event and we show an error message that reads, "The Xbox360 and Kinect content types must be present." Notice that there is no need to add the statement properties.Cancel = true.

  • To avoid the case in which ItemAdding calls itself recursively (up to a limit in the call stack), we disable event firing before we add items in the ItemAdding event. After all items are added, we re-enable event firing.

  • We use the expression properties.AfterProperties["field"] instead of properties.ListITem["field"] because ItemAdding is a Before event, which means that the list item is not yet created and properties.ListItem is null.

  • We do not want to add the item that we are splitting. Thus, we cancel the event without throwing an error by using the statement properties.Status = SPEventReceiverStatus.CancelNoError. Don't add properties.Cancel=true; doing so overrides the behavior and thus throws an error.

Now you are ready to build the solution and start the debugger. Doing so creates your solution, deploys it to the SharePoint site collection, and starts the browser on the home page of your site collection.

The next step is to create a new list and bind the three content types to it. First, ensure that content types are allowed to be added to the list. This can be done by setting Allow management of content types to Yes in the advanced settings page of the list. Then add the three content types as shown in Figure 10.

Figure 10. Binding content types

Binding content types

Now we can create a new item on the list based on the Xbox360Bundle content type as shown in Figure 11.

Figure 11. Creating a new item

Creating new item

When you click Save, you notice that the item is not added. Instead, two others items are added: Xbox360 and Kinect. If we remove either the Xbox360 or Kinect content type from the list, you get an error message when you attempt to save.

Example 3: Tracking Document Sources

Being able to use event receivers to manipulate document properties is a powerful feature. This example demonstrates how to do this by implementing property promotions and property demotion with event receivers. By using property promotion and demotion, you can read, add, or update document metadata directly from event receiver code at the time a document is added to a document library, or even when a document in a document library is edited.

In this example, we set the value of the Source metadata property to the document library title when we edit the document.

Click to get codeDownload the code sample: SharePoint 2010: Using Event Receivers, Example 3: Tracking Document Sources

Design

In this example we create an ItemUpdating event receiver that binds to all document libraries in the site. When a document is saved to the document library, the event handler code adds or updates the Source metadata property with the value of the document library title.

To test this behavior, we create two document libraries and add a Source field to each. Then we upload a Microsoft Word document to the first document library and save it. We then download this Word document, use the Word client to inspect its metadata, and upload the document to the other document library. On upload, the value of the Source field should still be equal to the title of the document library to which it was first uploaded. However, when we then save the document to the new document library, its Source property value should change to the title of the new document library. We inspect the metadata of the newly saved document to confirm this expectation.

Implementation

The following code implements the ItemUpdating event.

public override void ItemUpdating(SPItemEventProperties properties)
{
    string fieldInternalName = properties.List.Fields["Source"].InternalName;
    properties.AfterProperties[fieldInternalName] = properties.ListTitle;
}

Build the solution and start the debugger. These actions perform all of the work needed to create your solution, deploy it to the SharePoint site collection, and then start the browser on the home page of your site collection.

Create a new document library and add to it the Source field. To add the field, go to the document library settings, click Add from existing site columns, select the Source field, click Add, and click OK.

Next, upload a Word document to that document library. Notice that the value of the Source field is empty. When you click Save, however, the value of the Source field changes to the title of your document library.

Download a copy of the document to your local computer. Create a new document library and add the Source field. Upload the document to this new document library. On upload, notice that the value of the Source field still equals the title of the document library to which the document was last saved.

Now click Save. The value of the Source field changes to the title of your new document library.

Example 4: Canceling and Redirecting

This example is based on a scenario in which a user operation is cancelled (on error or for other reasons) and the user is redirected to a custom error page.

Click to get codeDownload the code sample: SharePoint 2010: Using Event Receivers, Example 4: Canceling and Redirecting

Design

In this example, we create a web event receiver and add code to the WebMoving event that cancels the act of renaming the site URL and redirects the user to the Site Settings page. (We use the Site Settings page for simplicity in this example. You might redirect the user to a custom error-message page.)

Implementation

Create a Visual Studio 2010 project based on the SharePoint 2010 Event Receiver project template. Choose to deploy your solution in the sandbox. In the Customization Wizard, select Web Events as the event source and A site is being moved as the event. Change the scope of the Feature to Site.

Add the following code, which cancels the rename event and redirects the user to the Site Settings page.

public override void WebMoving(SPWebEventProperties properties)
{
    properties.Status = SPEventReceiverStatus.CancelWithRedirectUrl;
    properties.RedirectUrl = properties.Web.ServerRelativeUrl +
      "/_layouts/settings.aspx";
}

Build the solution and start the debugger. This does all of the work needed to create your solution, deploy it to the SharePoint site collection, and then start the browser on the home page of your site collection.

Next, create a subsite and attempt to rename it. (We must create a new subsite because we cannot rename a root site.) To rename the site, go to the Site Settings page. In the Look and Feel section, click Title, description, and icon. In the URL name box, enter a new site name. Click OK.

The attempt to rename the site fails, and you are redirected to the Site Settings page.

Example 5: Logging Events

This example explores a scenario in which event-logging code captures every event that occurs on a site collection and records each event as an item on a list that is maintained on the site.

Click to get codeDownload the code sample: SharePoint 2010: Using Event Receivers, Example 5: Logging Events

Design

Table 5 lists all of the SharePoint events and identifies the list to which each event is logged. We are not logging the SiteDeleting or SiteDeleted event, because they are triggered when the site collection is deleted.

Table 5. Events and logs

Events

Class

Log list

SiteDeleting, SiteDeleted

SPWebEventReceiver

[not logged]

WebAdding, WebProvisioned, WebDeleting, WebDeleted, WebMoving, WebMoved

SPWebEventReceiver

WebEventLogger

ListAdding, ListAdded, ListDeleting, ListDeleted

SPListEventReceiver

ListEventLogger

EmailReceived

SPEmailEventReceiver

ListEventLogger

FieldAdding, FieldAdded, FieldUpdating, FieldUpdated, FieldDeleting, FieldDeleted

SPListEventReceiver

ListEventLogger

ItemAdding, ItemAdded, ItemUpdating, ItemUpdated, ItemDeleting, ItemDeleted, ItemCheckingIn, ItemCheckedIn, ItemCheckingOut, ItemCheckedOut, ItemFileMoving, ItemFileMoved, ItemFileConverted, ItemAttachmentAdding, ItemAttachmentAdded, ItemAttachmentDeleting, ItemAttachmentDeleted

SPItemEventReceiver

ListItemEventLogger

WorkflowStarting, WorkflowStarted, WorkflowCompleted, WorkflowPostponed

SPWorkflowEventReceiver

WorkflowEventLogger

In the example, we override all event methods of all classes that inherit from SPEventReceiverBase. We also create an additional class in Common.cs that contains helper methods. We then bind the After events as synchronous so that the corresponding lists get updated before the user operation is complete. The Feature that enables the event bindings is scoped to the Site level so that it handles all events triggered across all sites in the site collection.

Implementation

Create a Visual Studio 2010 project based on the Empty SharePoint Project template. Choose to deploy your solution in sandbox.

For each type of event in Table 6, right-click the project, click Add, point to New Item, and click Event Receiver. Select the event properties listed in the table, and click Finish.

Table 6. Properties for new event receivers

Type of events

Item

Events

Web

None

All events

List

None

All events

List item

Custom list

All events except context events

List email

Custom list

All events

List workflow

Custom list

All events

To handle all list item events, email events, and workflow events triggered on any list (not just custom lists), remove the ListTemplateId attributes from the <Receivers> elements. (Change <Receivers ListTemplateId="100"> to simply <Receivers>.)

By default, After events are asynchronous. All our After events need to be synchronous, so add the <Synchronization> element and set its value to "Synchronous" under each <Receiver> element in every Elements.xml file. For example, the WebDeleted event should resemble the following.

<Receiver>
  <Name>EventReceiver1WebDeleted</Name>
  <Type>WebDeleted</Type>
  <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>
  <Class>PTCEventLogger.EventReceiver1.EventReceiver1</Class>
  <SequenceNumber>10000</SequenceNumber>
  <Synchronization>Synchronous</Synchronization>
</Receiver>

Change the Feature scope to Site to handle events triggered across the whole site collection. Be aware that creating a new subsite means triggering events for site creation and everything else that gets added to the site. This might consume too many resources in a sandboxed solution and consequently the web creation could fail. You can work around this potential problem by changing Sandboxed Solution to False in the project properties or by changing the Feature scope back to Web.

Next, add a class named common to the project and place it in a file named Common.cs. Add the following three helper methods to this class.

  • IsBeforeEvent   Indicates whether an event is a Before event or an After event.

    /// <summary>
    /// Indicates whether an event is a Before event or an After event.
    /// </summary>
    /// <param name="eventType"></param>
    /// <returns></returns>
    private static bool IsBeforeEvent(SPEventReceiverType eventType)
    {
        return (eventType < SPEventReceiverType.ItemAdded) ? true : false;
    }
    
  • EnsureLogList   Determines whether a log list already exists. If it does, we get a reference to it. Otherwise, we create the list and return a reference to it.

    /// <summary>
    /// Ensures that the Logs list with the specified name is created.
    /// </summary>
    /// <param name="web"></param>
    /// <param name="listName"></param>
    /// <returns></returns>
    private static SPList EnsureLogList(SPWeb web, string listName)
    {
        SPList list = null;
        try
        {
            list = web.Lists[listName];
        }
        catch
        {
            // Create list.
            Guid listGuid = web.Lists.Add(listName, listName, SPListTemplateType.GenericList);
            list = web.Lists[listGuid];
            list.OnQuickLaunch = true;
    
            // Add the fields to the list.
            // No need to add "Title" because it is added by default.
            // We use it to set the event name.
            list.Fields.Add("Event", SPFieldType.Text, true);
            list.Fields.Add("Before", SPFieldType.Boolean, true);
            list.Fields.Add("Date", SPFieldType.DateTime, true);
            list.Fields.Add("Details", SPFieldType.Note, false);
    
            // Specify which fields to view.
            SPView view = list.DefaultView;
            view.ViewFields.Add("Event");
            view.ViewFields.Add("Before");
            view.ViewFields.Add("Date");
            view.ViewFields.Add("Details");
            view.Update();
    
            list.Update();
        }
    
        return list;
    }
    
  • LogEvent   Creates a new item on the specified list and logs the event details to that item.

    /// <summary>
    /// Log the event to the specified list.
    /// </summary>
    /// <param name="web"></param>
    /// <param name="listName"></param>
    /// <param name="eventType"></param>
    /// <param name="details"></param>
    public static void LogEvent(SPWeb web, string listName, SPEventReceiverType eventType, 
      string details)
    {
        SPList logList = Common.EnsureLogList(web, listName);
        SPListItem logItem = logList.Items.Add();
        logItem["Title"] = string.Format("{0} triggered at {1}", eventType, DateTime.Now);
        logItem["Event"] = eventType.ToString();
        logItem["Before"] = Common.IsBeforeEvent(eventType);
        logItem["Date"] = DateTime.Now;
        logItem["Details"] = details;
        logItem.Update();
    }
    

For each class that inherits from SPWebEventReceiver, SPListEventReceiver, SPEmailEventReceiver, SPItemEventReceiver, or SPWorkflowEventReceiver, you must create a Log method to handle the logging of the event properties to the corresponding list. You must also modify all of the overridden methods to call those Log methods. The following code listings show how to do this for each event receiver.

  • SPWebEventReceiver   Change all of the overridden events. Here is an example of how to change one of those events.

    public override void WebProvisioned(SPWebEventProperties properties)
    {
        this.LogWebEventProperties(properties);
    }
    

    Add the Log method, as shown in the following code.

    private void LogWebEventProperties(SPWebEventProperties properties)
    {
        // Specify the log list name.
        string listName = "WebEventLogger";
    
        // Create string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("FullUrl: {0}\n", properties.FullUrl);
        sb.AppendFormat("NewServerRelativeUrl: {0}\n", properties.NewServerRelativeUrl);
        sb.AppendFormat("ParentWebId: {0}\n", properties.ParentWebId);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("ServerRelativeUrl: {0}\n", properties.ServerRelativeUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("WebId: {0}\n", properties.WebId);
        sb.AppendFormat("Web: {0}\n", properties.Web);
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPListEventReceiver   Change all of the overridden events. Here is an example of how to change one of those events.

    public override void FieldAdded(SPListEventProperties properties)
    {
        this.LogListEventProperties(properties);
    }
    

    Add the Log method, as shown in the following code.

    private void LogListEventProperties(SPListEventProperties properties)
    {
        // Specify the log list name.
        string listName = "ListEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("FeatureId: {0}\n", properties.FeatureId);
        sb.AppendFormat("FieldName: {0}\n", properties.FieldName);
        sb.AppendFormat("FieldXml: {0}\n", properties.FieldXml);
        sb.AppendFormat("ListId: {0}\n", properties.ListId);
        sb.AppendFormat("ListTitle: {0}\n", properties.ListTitle);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("TemplateId: {0}\n", properties.TemplateId);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("WebId: {0}\n", properties.WebId);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
        sb.AppendFormat("Web: {0}\n", properties.Web);
        sb.AppendFormat("List: {0}\n", properties.List);
    
        // Add properties that might throw exceptions.
        try
        {
            sb.AppendFormat("Field: {0}\n", properties.Field);
        }
        catch (Exception e)
        {
            sb.AppendFormat("\nError accessing properties.Field:\n\n {0}", e);
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPEmailEventReceiver   Change the EmailReceived event as follows.

    public override void EmailReceived(SPList list, SPEmailMessage emailMessage, String receiverData)
    {
        // Specify the log list name.
        string listName = "ListEmailEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add the email message properties.
        sb.AppendFormat("From:\t {0}\n", emailMessage.Sender);
        sb.AppendFormat("To:\t {0}\n", emailMessage.Headers["To"]);
        sb.AppendFormat("Subject:\t {0}\n", emailMessage.Headers["Subject"]);
        sb.AppendFormat("Body:\t {0}\n", emailMessage.PlainTextBody);
    
        // Log the event to the list.
        Common.LogEvent(list.ParentWeb, listName, SPEventReceiverType.EmailReceived, sb.ToString());
    }
    
  • SPItemEventReceiver   Change all of the overridden events. Here is an example of how to change one of those events.

    public override void ItemAttachmentAdded(SPItemEventProperties properties)
    {
        this.LogItemEventProperties(properties);
    }
    Add the log method.
    private void LogItemEventProperties(SPItemEventProperties properties)
    {
        // Specify the log list name.
        string listName = "ListItemEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("AfterUrl: {0}\n", properties.AfterUrl);
        sb.AppendFormat("BeforeUrl: {0}\n", properties.BeforeUrl);
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("CurrentUserId: {0}\n", properties.CurrentUserId);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("ListId: {0}\n", properties.ListId);
        sb.AppendFormat("ListItemId: {0}\n", properties.ListItemId);
        sb.AppendFormat("ListTitle: {0}\n", properties.ListTitle);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("RelativeWebUrl: {0}\n", properties.RelativeWebUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("UserDisplayName: {0}\n", properties.UserDisplayName);
        sb.AppendFormat("UserLoginName: {0}\n", properties.UserLoginName);
        sb.AppendFormat("Versionless: {0}\n", properties.Versionless);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
        sb.AppendFormat("Web: {0}\n", properties.Web);
        sb.AppendFormat("Zone: {0}\n", properties.Zone);
        sb.AppendFormat("Context: {0}\n", properties.Context);
    
        // Add properties that might throw exceptions.
        try
        {
            sb.AppendFormat("ListItem: {0}\n", properties.ListItem);
        }
        catch (Exception e)
        {
            sb.AppendFormat("\nError accessing properties.ListItem:\n\n {0}", e);
        }
    
        // Add AfterProperties property bag.
        sb.AppendFormat("AfterProperties: {0}\n", properties.AfterProperties);
        IEnumerator afterProperties = properties.AfterProperties.GetEnumerator();
        int i = 0;
        while (afterProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)afterProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
        sb.AppendFormat("AfterProperties.ChangedProperties: {0}\n", 
          properties.AfterProperties.ChangedProperties);
        IEnumerator changedAfterProperties = 
          properties.AfterProperties.ChangedProperties.GetEnumerator();
        i = 0;
        while (changedAfterProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)changedAfterProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
    
        // Add BeforeProperties property bag.
        sb.AppendFormat("BeforeProperties: {0}\n", properties.BeforeProperties);
        IEnumerator beforeProperties = properties.BeforeProperties.GetEnumerator();
        i = 0;
        while (beforeProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)beforeProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
        sb.AppendFormat("BeforeProperties.ChangedProperties: {0}\n", 
          properties.BeforeProperties.ChangedProperties);
        IEnumerator changedBeforeProperties = 
          properties.BeforeProperties.ChangedProperties.GetEnumerator();
        i = 0;
        while (changedBeforeProperties.MoveNext())
        {
            DictionaryEntry entry = (DictionaryEntry)changedBeforeProperties.Current;
            sb.AppendFormat("[{0}]: ({1}, {2})\n", i++, entry.Key, entry.Value);
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.Web, listName, properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    
  • SPWorkflowEventReceiver   Change all of the overridden events. Here is an example of how to change one of those events.

    public override void WorkflowStarting(SPWorkflowEventProperties properties)
    {
        this.LogWorkflowEventProperties(properties);
    }
    

    Add the Log method, as shown in the following code.

    private void LogWorkflowEventProperties(SPWorkflowEventProperties properties)
    {
        // Specify the log list name.
        string listName = "WorkflowEventLogger";
    
        // Create the string builder object.
        StringBuilder sb = new StringBuilder();
    
        // Add properties that do not throw exceptions.
        sb.AppendFormat("AssociationData: {0}\n", properties.AssociationData);
        sb.AppendFormat("Cancel: {0}\n", properties.Cancel);
        sb.AppendFormat("CompletionType: {0}\n", properties.CompletionType);
        sb.AppendFormat("ErrorMessage: {0}\n", properties.ErrorMessage);
        sb.AppendFormat("EventType: {0}\n", properties.EventType);
        sb.AppendFormat("InitiationData: {0}\n", properties.InitiationData);
        sb.AppendFormat("InstanceId: {0}\n", properties.InstanceId);
        sb.AppendFormat("PostponedEvent: {0}\n", properties.PostponedEvent);
        sb.AppendFormat("ReceiverData: {0}\n", properties.ReceiverData);
        sb.AppendFormat("RedirectUrl: {0}\n", properties.RedirectUrl);
        sb.AppendFormat("RelativeWebUrl: {0}\n", properties.RelativeWebUrl);
        sb.AppendFormat("SiteId: {0}\n", properties.SiteId);
        sb.AppendFormat("Status: {0}\n", properties.Status);
        sb.AppendFormat("TerminatedByUserId: {0}\n", properties.TerminatedByUserId);
        sb.AppendFormat("WebUrl: {0}\n", properties.WebUrl);
    
        // Get activation properties.
        SPWorkflowActivationProperties activationProperties = properties.ActivationProperties;
        if (activationProperties != null)
        {
            sb.AppendFormat("ActivationProperties.AssociationData: {0}\n", 
              activationProperties.AssociationData);
            sb.AppendFormat("ActivationProperties.HistoryListId: {0}\n", 
              activationProperties.HistoryListId);
            sb.AppendFormat("ActivationProperties.HistoryListUrl: {0}\n", 
              activationProperties.HistoryListUrl);
            sb.AppendFormat("ActivationProperties.InitiationData: {0}\n", 
              activationProperties.InitiationData);
            sb.AppendFormat("ActivationProperties.ItemId: {0}\n", 
              activationProperties.ItemId);
            sb.AppendFormat("ActivationProperties.ItemUrl: {0}\n", 
              activationProperties.ItemUrl);
            sb.AppendFormat("ActivationProperties.ListId: {0}\n", 
              activationProperties.ListId);
            sb.AppendFormat("ActivationProperties.ListUrl: {0}\n", 
              activationProperties.ListUrl);
            sb.AppendFormat("ActivationProperties.Originator: {0}\n", 
              activationProperties.Originator);
            sb.AppendFormat("ActivationProperties.OriginatorEmail: {0}\n", 
              activationProperties.OriginatorEmail);
            sb.AppendFormat("ActivationProperties.SiteId: {0}\n", 
              activationProperties.SiteId);
            sb.AppendFormat("ActivationProperties.SiteUrl: {0}\n", 
              activationProperties.SiteUrl);
            sb.AppendFormat("ActivationProperties.TaskListId: {0}\n", 
              activationProperties.TaskListId);
            sb.AppendFormat("ActivationProperties.TaskListUrl: {0}\n", 
              activationProperties.TaskListUrl);
            sb.AppendFormat("ActivationProperties.TemplateName: {0}\n", 
              activationProperties.TemplateName);
            sb.AppendFormat("ActivationProperties.WebId: {0}\n", 
              activationProperties.WebId);
            sb.AppendFormat("ActivationProperties.WebUrl: {0}\n", 
              activationProperties.WebUrl);
            sb.AppendFormat("ActivationProperties.WorkflowId: {0}\n", 
              activationProperties.WorkflowId);
    
            // Add properties that might throw exceptions.
            try
            {
                sb.AppendFormat("ActivationProperties.Context: {0}\n", 
                  activationProperties.Context);
                sb.AppendFormat("ActivationProperties.HistoryList: {0}\n", 
                  activationProperties.HistoryList);
                sb.AppendFormat("ActivationProperties.Item: {0}\n", 
                  activationProperties.Item);
                sb.AppendFormat("ActivationProperties.List: {0}\n", 
                  activationProperties.List);
                sb.AppendFormat("ActivationProperties.OriginatorUser: {0}\n", 
                  activationProperties.OriginatorUser);
                sb.AppendFormat("ActivationProperties.Site: {0}\n", 
                  activationProperties.Site);
                sb.AppendFormat("ActivationProperties.TaskList: {0}\n", 
                  activationProperties.TaskList);
                sb.AppendFormat("ActivationProperties.Web: {0}\n", 
                  activationProperties.Web);
                sb.AppendFormat("ActivationProperties.Workflow: {0}\n", 
                  activationProperties.Workflow);
            }
            catch (Exception e)
            {
                sb.AppendFormat("\nError accessing ActivationProperties property:\n\n {0}", e);
            }
        }
        else
        {
            sb.AppendFormat("ActivationProperties is null\n");
        }
    
        // Log the event to the list.
        this.EventFiringEnabled = false;
        Common.LogEvent(properties.ActivationProperties.Web, listName, 
          properties.EventType, sb.ToString());
        this.EventFiringEnabled = true;
    }
    

Conclusion

You now know the entire SharePoint 2010 event model, how to create event receivers, and how to deploy projects that implement event receivers. (You also know how much work you can save by using the Event Receiver project template in Visual Studio 2010.) And you have several practical examples that you can adapt to your own requirements. If you need even more details about the SharePoint event model, dive into the SharePoint 2010 SDK, starting with Events in SharePoint Foundation 2010.

Additional Resources

For more information, see the following resources: