This documentation is archived and is not being maintained.

Chapter 9: Event Receivers Part 2 of 2 (Expert WSS 3.0 and MOSS 2007 Programming)

Windows SharePoint Services 3

This article is an excerpt from Expert WSS 3.0 and MOSS 2007 Programming, by Dr. Shahram Khosravi, from Wrox (ISBN 978-0470381373, copyright Wrox 2008, all rights reserved). No part of these chapters may be reproduced, stored in a retrieval system, or transmitted in any form or by any means—electronic, electrostatic, mechanical, photocopying, recording, or otherwise—without the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

The previous section showed you how to implement your own custom list event handlers and bind them to a list type, list instance, site content type, or list content type. This section shows you how to implement your own custom item event handlers and bind them to a site content type or a list.

The first order of business is to implement an item event receiver, which is a class that inherits directly or indirectly from a base class named SPItemEventReceiver as defined in Listing 9-14.

Listing 9-14: The SPItemEventReceiver class

[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true),
SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true)]
public class SPItemEventReceiver : SPEventReceiverBase
{
    public virtual void ItemAdded(SPItemEventProperties properties);
    public virtual void ItemAdding(SPItemEventProperties properties);
    public virtual void ItemAttachmentAdded(SPItemEventProperties properties);
    public virtual void ItemAttachmentAdding(SPItemEventProperties properties);
    public virtual void ItemAttachmentDeleted(SPItemEventProperties properties);
    public virtual void ItemAttachmentDeleting(SPItemEventProperties properties);
    public virtual void ItemCheckedIn(SPItemEventProperties properties);
    public virtual void ItemCheckedOut(SPItemEventProperties properties);
    public virtual void ItemCheckingIn(SPItemEventProperties properties);
    public virtual void ItemCheckingOut(SPItemEventProperties properties);
    public virtual void ItemDeleted(SPItemEventProperties properties);
    public virtual void ItemDeleting(SPItemEventProperties properties);
    public virtual void ItemFileConverted(SPItemEventProperties properties);
    public virtual void ItemFileMoved(SPItemEventProperties properties);
    public virtual void ItemFileMoving(SPItemEventProperties properties);
    public virtual void ItemUncheckedOut(SPItemEventProperties properties);
    public virtual void ItemUncheckingOut(SPItemEventProperties properties);
    public virtual void ItemUpdated(SPItemEventProperties properties);
    public virtual void ItemUpdating(SPItemEventProperties properties);
}

The methods of SPItemEventReceiver are known as item event handlers because each method is automatically invoked when its respective event is fired. For example, the ItemAdded event handler is invoked after an item is added to the SharePoint list. Because none of the methods of this base class are marked as abstract, your custom item event receiver can override the desired item event handlers without having to provide implementation for all these event handlers. This is very different from a feature receiver where your custom feature receiver must implement all four methods of the SPFeatureReceiver base class.

Here are the descriptions of some of the event handlers that SPItemEventReceiver contains:

  • ItemAdding. This is invoked before an item is added to the respective list. Your event receiver's implementation of this event handler can run application-specific code to determine whether to allow the item to be added.

  • ItemAdded. This is invoked after an item is added to the respective list.

  • ItemDeleting. This is invoked before an item is deleted from the list. Your event receiver's implementation of this event handler can execute application-specific validation logic and cancel the item deletion operation if it fails to validate.

  • ItemDeleted. This is invoked after an item is deleted from the list.

  • ItemUpdating. This is invoked before an item in the list is updated. Your event receiver's implementation of this event handler can execute application-specific validation logic and cancel the item update operation if it fails to validate.

  • ItemUpdated. This is invoked after an item in the list is updated.

  • ItemCheckingIn. This is invoked before an item is checked in. Your event receiver's implementation of this event handler can execute application-specific validation logic and cancel the item check-in operation if it fails to validate.

  • ItemCheckedIn. This handler is invoked after an item is checked in.

  • ItemCheckingOut. This is invoked before an item is checked out. Your event receiver's implementation of this event handler can execute application-specific validation logic and cancel the item check-out operation if it fails to validate.

  • ItemCheckedOut. This event handler is invoked after an item is checked out.

Note that all the event handlers of SPItemEventReceiver take a single argument of type SPItemEventProperties as defined in Listing 9-15.

Listing 9-15: The SPItemEventProperties type

[SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true),
SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true)]
public sealed class SPItemEventProperties : SPEventPropertiesBase, IDisposable
{
    [SharePointPermission(SecurityAction.Demand, ObjectModel=true)]
    public void Dispose();
    public SPWeb OpenWeb();
    public SPItemEventDataCollection AfterProperties { get; }
    public string AfterUrl { get; }
    public SPItemEventDataCollection BeforeProperties { get; }
    public string BeforeUrl { get; }
    public int CurrentUserId { get; }
    public SPEventReceiverType EventType { get; }
    public Guid ListId { get; }
    public SPListItem ListItem { get; }
    public int ListItemId { get; }
    public string ListTitle { get; }
    public string RelativeWebUrl { get; }
    public Guid SiteId { get; }
    public string UserDisplayName { get; }
    public string UserLoginName { get; }
    public bool Versionless { get; }
    public string WebUrl { get; }
}

Here are the descriptions of the properties of SPItemEventProperties:

  • AfterUrl. This gets the URL of the item after the event is fired.

  • BeforeUrl. This gets the URL of the item before the event is fired.

  • CurrentUserId. This gets the current user ID, which is the user ID of the user who caused the event to fire.

  • EventType. This gets the type of the event.

  • ListId. This gets the GUID identifier of the SharePoint list to which the item belongs.

  • ListItem. This gets a reference to the SPListItem object that represents the item.

  • ListItemId. This gets the integer identifier of the item.

  • ListTitle. This gets the title of the SharePoint list to which the item belongs.

  • RelativeWebUrl. This gets the server-relative URL of the site in which the event occurs.

  • SiteId. This gets the GUID identifier of the site collection in which the event is fired.

  • UserDisplayName. This gets the display name of the user that caused the event to fire.

  • UserLoginName. This gets the login name of the user that caused the event to fire.

  • WebUrl. This gets the absolute URL of the site where the event is fired.

  • AfterProperties. This gets a reference to the SPItemEventDataCollection object that contains the field names and values of the item after the event is fired.

  • BeforeProperties. This gets a reference to the SPItemEventDataCollection object that contains the field names and values of the item before the event is fired.

The SPItemEventDataCollection class internally stores field names and values in a hashtable. This class exposes an indexer property that takes the name of a field and returns its value.

Next, you'll implement a custom item event receiver named MyItemEventReceiver and deploy it to SharePoint. Listing 9-16 presents the implementation of your custom item event receiver. Go ahead and launch Visual Studio 2008 and add a new Class Library project named ClassLibrary2. Add a reference to Microsoft.SharePoint.dll assembly. Then configure Visual Studio to compile the project into a strong named assembly and add a post event to have Visual Studio automatically install the assembly in the GAC every time the project is built. Next change the name of the Class1.cs file to MyItemEventReceiver.cs and add the content of Listing 9-16 to this file.

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Net.Mail;
namespace ClassLibrary2.ItemEventReceiver
{
    public class MyItemEventReceiver : SPItemEventReceiver
    {
        private bool ValidateFieldValues(SPItemEventDataCollection fields,
            out string errorMessage)
        {
            // Use application-specific validation logic to validate field values and to
            // set the error message if any
            errorMessage = "";
            double myColumnValue = double.Parse(fields["MyColumn1"].ToString());
            if (myColumnValue > 100)
            {
                errorMessage =
                    "The value of the MyColumn1 column cannot be greater than 100!";
                return false;
            }
            return true;
        }
        public override void ItemAdding(SPItemEventProperties properties)
        {
            string errorMessage;
            if (!ValidateFieldValues(properties.AfterProperties, out errorMessage))
            {
                properties.ErrorMessage = errorMessage;
                properties.Status = SPEventReceiverStatus.CancelWithError;
                properties.Cancel = true;
            }
        }
        public override void ItemAdded(SPItemEventProperties properties)
        {
            string body = "The new item " + properties.ListItem.Title +
                          " was added to the list " + properties.ListTitle;
            MailMessage message = new MailMessage("admin@somewhere.com",
                "EnterToEmailAddress", "New Item Added", body);
            SmtpClient client = new SmtpClient();
            client.Send(message);
        }
        public override void ItemUpdating(SPItemEventProperties properties)
        {
            string errorMessage;
            if (!ValidateFieldValues(properties.AfterProperties, out errorMessage))
            {
                properties.ErrorMessage = errorMessage;
                properties.Status = SPEventReceiverStatus.CancelWithError;
                properties.Cancel = true;
            }
        }
        public override void ItemUpdated(SPItemEventProperties properties)
        {
            string body = "The item " + properties.ListItem.Title + " in list " +
properties.ListTitle + " was updated";
            MailMessage message = new MailMessage("admin@somewhere.com",
                "EnterToEmailAddress", "Item Updated", body);
            SmtpClient client = new SmtpClient();
            client.Send(message);
        }
    }
}

MyItemEventReceiver, like any other item event receiver, inherits from SPItemEventReceiver.

MyItemEventReceiver contains four event handlers: ItemAdding, ItemAdded, ItemUpdating, and ItemUpdated. Note that both the ItemAdding and ItemUpdating event handlers use a private method named ValidateFieldValues, which contains application-specific validation logic to validate the fieldvalues of the item being added. The field values are contained in the AfterProperties property of the

SPItemEventProperties object that SharePoint passes into the ItemAdding and ItemUpdating eventhandlers.

As Listing 9-16 shows, ItemAdding and ItemUpdating first validate the field values. If this validation fails, they set the ErrorMessage and Status properties of the SPItemEventProperties object and cancel the add or update operation:

properties.ErrorMessage = errorMessage;
properties.Status = SPEventReceiverStatus.CancelWithError;
properties.Cancel = true;

ItemAdded and ItemUpdated use the ASP.NET MailMessage and SmtpClient classes to send an email to a specified email address stating that a new item is added or updated. The constructor of the MailMessage takes the following four parameters:

  • From email address

  • To email address

  • Email subject

  • Email body

Note that ItemAdded and ItemUpdated use the Title property of the ListItem property of the SPItemEventReceiver object and the ListTitle property of this object to access the titles of the item and the list that contains the item. They then include these two titles in the email body:

string body = "The item " + properties.ListItem.Title + " in list " +
    properties.ListTitle + " was updated";

SmtpClient uses the configuration settings specified in the web.config file of the web application to determine the SMTP outgoing server and the credentials (if necessary). Listing 9-17 presents the section of the web.config file that contains these settings.

Listing 9-17: A portion of web.config

<configuration>
    <system.net>
        <mailSettings>
            <smtp>
                <network host="HostGoesHere" port="25" />
            </smtp>
        </mailSettings>
    </system.net>
</configuration>

As you can see, <system.ne> contains a child element named <mailSettings>, which contains a child element named <smtp>, which in turn contains a child element named <network>, which is used to specify the SMTP outgoing mail server. <network> supports the following two attributes:

  • host. You'll want to assign the address of the SMTP outgoing mail server to this attribute.

  • port. The port number 25 is normally used.

You can use the STSADM command-line utility that you developed earlier to add the <mailSettings> entry to the web.config file of the web application.

Next, you'll need to compile MyItemEventReceiver shown in Listing 9-16 to a strong-named assembly and install this assembly in the Global Assembly Cache. Next, you'll see how to bind the item event handlers of your item event receiver. You have several options that are discussed in the following sections.

Binding Item Event Handlers to a Site Content Type

The first option is to bind these item event handlers to a site content type. Here is what you need to do to bind your item event handlers to a site content type. First, you need to implement a feature that references an element manifest file as shown in Listing 9-18.

Listing 9-18: The feature.xml file

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="{324ED9EA-7B95-4c79-AB4F-0BB8E648096D}"
Hidden="False"
Description="Binds item event handlers to your site content type"
Title="Binds item event handlers to your site content type"
Scope="Site">
    <ElementManifests>
        <ElementManifest Location="elements.xml" />
    </ElementManifests>
</Feature>

Next, you need to implement this element manifest file as shown in Listing 9-19.

Listing 9-19: The element manifest file

<?xml version="1.0" encoding="utf-8" ? >
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <ContentType ID="0x010409"
    Name="NewAnnouncementsType"
    Group="My Custom Content Types"
    Description="Provisions a NewAnnouncementsType content type."
    Version="0">
        <FieldRefs>
            <FieldRef ID="{A3C0A7D7-5D3B-4966-AB64-FEF29ECEBDBC}" Name="MyColumn1" />
        </FieldRefs>
        <XmlDocuments>
            <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/events">
                <Receivers xmlns="http://schemas.microsoft.com/sharepoint/events">
                    <Receiver>
                        <Name> Item Adding </Name>
                        <Type> ItemAdding </Type>
                        <Class> ClassLibrary2.ItemEventReceiver.MyItemEventReceiver </Class>
                        <Assembly> ClassLibrary2, Version=1.0.0.0, Culture=neutral,
                        PublicKeyToken=fa0f9b97611d4862 </Assembly>
                    </Receiver>
                    <Receiver>
                        <Name> Item Added </Name>
                        <Type> ItemAdded </Type>
                        <Class> ClassLibrary2.ItemEventReceiver.MyItemEventReceiver </Class>
                        <Assembly> ClassLibrary2, Version=1.0.0.0, Culture=neutral,
                        PublicKeyToken=fa0f9b97611d4862 < /Assembly >
                    </Receiver>
                    <Receiver>
                        <Name> Item Updating </Name>
                        <Type> ItemUpdating </Type>
                        <Class> ClassLibrary2.ItemEventReceiver.MyItemEventReceiver </Class>
                        <Assembly> ClassLibrary2, Version=1.0.0.0, Culture=neutral,
                        PublicKeyToken=fa0f9b97611d4862 < /Assembly >
                    </Receiver>
                    <Receiver>
                        <Name> Item Updated </Name>
                        <Type> ItemUpdated </Type>
                        <Class> ClassLibrary2.ItemEventReceiver.MyItemEventReceiver </Class>
                        <Assembly> ClassLibrary2, Version=1.0.0.0, Culture=neutral,
                        PublicKeyToken=fa0f9b97611d4862 </Assembly>
                    </Receiver>
                </Receivers>
            </XmlDocument>
        </XmlDocuments>
    </ContentType>
    <Field ID="{A3C0A7D7-5D3B-4966-AB64-FEF29ECEBDBC}"
    Name="MyColumn1"
    SourceID="http://schemas.microsoft.com/sharepoint/v3"
    StaticName="MyColumn1"
    Group="My Site Columns"
    RowOrdinal="0"
    DisplayName="My Column1"
    Type="Number"
    Hidden="False" >
    </Field>
</Elements>

As Listing 9-19 shows, this element manifest file defines a custom site content type named NewAnnouncementsType with the content type ID of 0x010405, which inherits from the Announcements site content type (0x0104) and adds a reference to a custom site column named MyColumn1. Note that this element manifest file also contains the definition of the MyColumn1 custom site column.

As discussed earlier, the <ContentType> element that defines a site content type supports a child element named <XmlDocuments>, which in turn supports one or more instances of a child element named <XmlDocument>. As thoroughly discussed earlier, you can use an XML document with the document element named <Receivers> within the opening and closing tags of an <XmlDocument> to bind your event handlers to the site content type.

Next, you need to deploy the feature file shown in Listing 9-18 and the element manifest file shown in Listing 9-19 to a feature-specific folder in the Features system folder in the file system of each front-end web server in the server farm. Next, you need to use the installfeature and activatefeature operations of the STSADM command-line utility to install and to activate this feature. After installing and activating the feature in your favorite site, add the NewAnnouncementsType site content type to your favorite list. This will automatically create a local copy of this site content type known as a list content type and add this local copy to your list. Then try to add an item with the NewAnnouncementsType content type with the MyColumn1 field value greater than 100. You should be directed to a page that displays the error message that the respective event handler assigned to the ErrorMessage property of the SPListEventProperties object. Next, add an item with the NewAnnouncementsType content type with the MyColumn1 field value less than 100. You should get an email from SharePoint stating that the item has been added to the list.

Binding Item Event Handlers to a List Instance

Another option is to bind your item event handlers to a specific list in your site. First, you'll need to implement a feature that uses a feature receiver as shown in Listing 9-20.

Listing 9-20: The feature.xml file

< ?xml version="1.0" encoding="utf-8" ? >
< Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="{71376DA3-C6D7-4ab1-88D2-1645BFD32C64}"
Title="Installs MyNewFeatureReceiver2"
Description="Installs MyNewFeatureReceiver2" Hidden="False" Version="1.0.0.0"
Scope="Site"
ReceiverClass="ClassLibrary2.MyNewFeatureReceiver2"
ReceiverAssembly=" ClassLibrary2, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=fa0f9b97611d4862" >
< /Feature >

Listing 9-21 contains the code for the feature receiver that this feature uses.

using System;
using Microsoft.SharePoint;
namespace ClassLibrary2
{
    public class MyNewFeatureReceiver2: SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPSite siteCollection = properties.Feature.Parent as SPSite;
            SPWeb site = siteCollection.OpenWeb();
            SPList list = site.Lists["MyList100"];
            SPEventReceiverDefinition rd = list.EventReceivers.Add();
            rd.Name = "My Item Event Receiver";
            rd.Class = " ClassLibrary2.ItemEventReceiver.MyItemEventReceiver";
            rd.Assembly = " ClassLibrary2, Version=1.0.0.0, Culture=neutral,
            PublicKeyToken=fa0f9b97611d4862";
            rd.Data = "My Event Receiver data";
            rd.Type = SPEventReceiverType.ItemAdded;
rd.Update();
        }
        public override void FeatureDeactivating(
            SPFeatureReceiverProperties properties)
        {
            SPSite sitecollection = properties.Feature.Parent as SPSite;
            SPWeb site = sitecollection.OpenWeb();
            SPList list = site.Lists["MyList100"];
            foreach (SPEventReceiverDefinition rd in list.EventReceivers)
            {
                if (rd.Name == "My Item Event Receiver")
                    rd.Delete();
            }
        }
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }
        public override void FeatureUninstalling(
            SPFeatureReceiverProperties properties)
        {
        }
    }
}

Next, you'll walk through the implementation of the FeatureActivated method. This method first accesses the SPList object that represents the SharePoint list as usual:

SPSite siteCollection = properties.Feature.Parent as SPSite;
SPWeb site = siteCollection.OpenWeb();
SPList list = site.Lists["MyList100"];

Then, it invokes the Add method on the EventReceivers collection property of this SPList object to instantiate and to return a reference to an SPEventReceiverDefinition object, which will be used to bind the ItemAdded event handler to the list:

SPEventReceiverDefinition rd = list.EventReceivers.Add();

Finally, it sets the properties of this SPEventReceiverDefinition object and invokes the Update method on the object as usual:

rd.Name = "My Item Event Receiver";
rd.Class = "ClassLibrary2.ItemEventReceiver.MyItemEventReceiver";
rd.Assembly = "ClassLibrary2, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=fa0f9b97611d4862";
rd.Data = "My Event Receiver data";
rd.Type = SPEventReceiverType.ItemAdded;
rd.Update();

Next, you need to build the project. Then, you need to deploy the feature file shown in Listing 9-20 to a feature-specific folder in the Features system folder in the file system of each front-end web server in the server farm. Next, you need to use the installfeature operation of the STSADM command-line utility to install this feature. Then add a new list named MyList100 to your favorite site. Next add a new column named MyColumn1 to this list. Keep in mind that this column must be a Number column. Finally, activate the feature in this site. Now if you add an item to the list, you should get an email stating that the specified item has been added to the list.

This section shows you how to implement web event handlers and bind them to a site. The first order of business is to implement a class known as a web event receiver, which inherits from a base class named SPWebEventReceiver as defined in Listing 9-22.

Listing 9-22: The SPWebEventReceiver class

[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true),
SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true)]
public class SPWebEventReceiver : SPEventReceiverBase
{
    public virtual void SiteDeleted(SPWebEventProperties properties);
    public virtual void SiteDeleting(SPWebEventProperties properties);
    public virtual void WebDeleted(SPWebEventProperties properties);
    public virtual void WebDeleting(SPWebEventProperties properties);
    public virtual void WebMoved(SPWebEventProperties properties);
    public virtual void WebMoving(SPWebEventProperties properties);
}

The methods of SPWebEventReceiver are known as web event handlers because they're automatically invoked when their respective events are fired. Here are the descriptions of these web event handlers:

  • SiteDeleting. This web event handler is invoked before a site collection is deleted. Because this event handler handles the SiteDeleting Before event, your web event receiver's override of this event handler can cancel the site collection deletion if it violates application-specific validation logic.

  • SiteDeleted. This web event handler is invoked after a site collection is deleted.

  • WebDeleting. This web event handler is invoked before a site is deleted. Because this event handler handles the WebDeleting Before event, your web event receiver's override of this event handler can cancel the site deletion if it violates application-specific validation logic.

  • WebDeleted. This web event handler is invoked after a site is deleted.

  • WebMoving. This web event handler is invoked before a site is moved. Because this event handler handles the WebMoving Before event, your web event receiver's override of this event handler can cancel the site move if it violates application-specific validation logic.

  • WebMoved. This web event handler is invoked after a site is moved.

As Listing 9-22 shows, all web event handlers take a single argument of type SPWebEventProperties, which is defined as shown in Listing 9-23.

Listing 9-23: The SPWebEventProperties type

[SharePointPermission(SecurityAction.InheritanceDemand, ObjectModel=true),
SharePointPermission(SecurityAction.LinkDemand, ObjectModel=true)]
public sealed class SPWebEventProperties : SPEventPropertiesBase
{
    public SPEventReceiverType EventType { get; }
    public string FullUrl { get; }
    public string NewServerRelativeUrl { get; }
    public string ServerRelativeUrl { get; }
    public Guid SiteId { get; }
    public string UserDisplayName { get; }
    public string UserLoginName { get; }
    public SPWeb Web { get; }
    public Guid WebId { get; }
}

Here are the descriptions of the properties of SPWebEventProperties type:

  • EventType. This gets an SPEventReceiverType enumeration value that specifies the type of the event.

  • FullUrl. This gets the absolute URL of the site that fired the event.

  • NewServerRelativeUrl. This gets the server- relative URL of the site after it has moved.

  • ServerRelativeUrl. This gets the server-relative URL of the site that fired the event.

  • SiteId. This gets the GUID identifier of the site collection in which the event was fired.

  • UserDisplayName. This gets the display name of the user that caused the event to fire.

  • UserLoginName. This gets the login name of the user that caused the event to fire.

  • Web. This gets a reference to the SPWeb object that represents the site that fired the event.

  • WebId. This gets the GUID identifier of the site that fired the event.

Next, you'll implement a custom web event receiver named MyWebEventReceiver. Listing 9-24 presents the implementation of this web event receiver. Go ahead and create a new Class Library project named ClassLibrary3. Rename the Class1.cs file to MyWebWebEventReceiver.cs and copy the content of Listing 9-24 into this file. Next, configure Visual Studio to compile the project into strong-named assembly and add a post event to have Visual Studio install the assembly in the GAC every time the project is built. Don't forget to add a reference to the Microsoft.SharePoint.dll assembly.

Listing 9-24: The MyWebEventReceiver web event receiver

using System;
using Microsoft.SharePoint;
using System.Net.Mail;
namespace ClassLibrary3
{
    public class MyWebEventReceiver : SPWebEventReceiver
    {
        public override void WebDeleted(SPWebEventProperties properties)
        {
            string body = "The site " + properties.FullUrl + " was deleted.";
            MailMessage message = new MailMessage("admin@somewhere.com",
                "ToEmailAddress", "Site Deleted", body);
            SmtpClient client = new SmtpClient();
            client.Send(message);
        }
    }
}

This web event receiver contains a single web event handler called WebDeleted. As you can see, this web event handler sends an email to a specified email address when a site is deleted. Note that this web event handler contains the value of the FullUrl property of the SPWebEventProperties object in the email body.

Next, you'll see how to bind this web event handler to a site. You have several options, as discussed in the following sections.

Binding a Web Event Handler to an Existing Site

You have several options for binding a web event handler to an existing site. One option is to use a feature receiver. Another option is to implement a console application. The latter option requires console access to the web server, but many system administrators do not like to give console access to developers. In this section, you'll use the former option. Here is how it works.

First, implement a feature that uses a feature receiver as shown in Listing 9-25.

Listing 9-25: The feature.xml file

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="{F398089B-CDB7-43f2-9E16-1651990EB887}"
Hidden="False"
Description="Binds custom web event handlers"
Title="Custom web Event Handlers Installer"
Scope="Web"
ReceiverClass="ClassLibrary3.MyNewFeatureReceiver3"
ReceiverAssembly="ClassLibrary3, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=df663a77cbaeeca1" >
</Feature>

Listing 9-26 presents the implementation of this feature receiver. Go ahead and add a new source file named MyNewFeatureReceiver3.cs to the project and add the content of Listing 9-26 to this file.

Listing 9-26: The feature receiver

using System;
using Microsoft.SharePoint;
namespace ClassLibrary3
{
    public class MyNewFeatureReceiver3: SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWeb site = properties.Feature.Parent as SPWeb;
            SPEventReceiverDefinition rd = site.EventReceivers.Add();
            rd.Name = "My Event Receiver";
            rd.Class = "ClassLibrary3.MyWebEventReceiver";
            rd.Assembly = "ClassLibrary3, Version=1.0.0.0, Culture=neutral,
                PublicKeyToken=df663a77cbaeeca1";
            rd.Data = "My Event Receiver data";
            rd.Type = SPEventReceiverType.WebDeleted;
            rd.Update();
        }
        public override void FeatureDeactivating(
            SPFeatureReceiverProperties properties)
        {
            SPWeb site = properties.Feature.Parent as SPWeb;
            foreach (SPEventReceiverDefinition rd in site.EventReceivers)
            {
                if (rd.Name == "My Event Receiver")
                    rd.Delete();
            }
        }
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }
        public override void FeatureUninstalling(
            SPFeatureReceiverProperties properties)
        {
        }
    }
}

As you can see, the FeatureActivated method first accesses a reference to the SPWeb object that represents the current web site:

SPWeb site = properties.Feature.Parent as SPWeb;

Then, it invokes the Add method on the EventReceivers collection property of this SPWeb object to instantiate and to return a reference to the SPEventReceiverDefinition object that you will use to bind your web event handler to the current site. As you can see, the EventReceivers collection property of an SPWeb object contains references to the SPEventReceiverDefinition objects that bind web event handlers to the site that the SPWeb object represents:

SPEventReceiverDefinition rd = site.EventReceivers.Add(); 

Next, FeatureActivated specifies a unique name for this SPEventReceiverDefinition:

rd.Name = "My Event Receiver";

Then, it specifies the MyWebEventReceiver class as the web event receiver:

rd.Class = "ClassLibrary3.MyWebEventReceiver";
rd.Assembly = "ClassLibrary3, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=df663a77cbaeeca1";

Next, it specifies the WebDeleted web event handler as the web event handler that you want to bind to the current site:

rd.Type = SPEventReceiverType.WebDeleted;

Next, you need to build the project. Then, you need to deploy the feature file shown in Listing 9-25 to a feature-specific folder in the Features system folder in the file system of each front - end web server in the server farm. Next, you need to use the installfeature and activatefeature operations of the STSADM command-line utility to install and to activate this feature. After installing and activating the feature in your favorite site, go ahead and delete the site. You should get an email stating that the specified site has been deleted.

Binding a Web Event Handler to an Existing Site Definition Configuration

The previous section implemented a feature that uses a feature receiver to bind a web event handler to a specific site. This section shows you how to use the same feature to bind the web event handler to an existing site definition configuration as opposed to a specific site. When you bind a web event handler to a site definition configuration it gets bound to all sites provisioned from that site definition configuration.

Listing 9-27 presents a stapling feature that staples your web event handler to the STS#0 site definition configuration. Introduced in Chapter 8, feature stapling allows you to attach a feature to a site definition configuration without modifying it so that you can add this feature to all new instances of sites created using that site definition. Sites already created will not have the feature activated.

Listing 9-27: The stapling feature

<?xml version="1.0" encoding="utf-8" ?>
<Feature
Id="{D245F7A1-C95A-4b25-9378-FB171C03E5C9}"
Title="Stapling Feature"
Description="This feature staples your feature to Team sites"
Version="1.0.0.0"
Scope="Site"
xmlsn="http://schemas.microsoft.com/sharepoint/">
    <ElementManifests>
        <ElementManifest Location="elements.xml"/>
    </ElementManifests>
</Feature>

Listing 9-28 presents the content of the element manifest file that your stapling feature references.

Listing 9-28: The element manifest file

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <FeatureSiteTemplateAssociation
    Id="{F398089B-CDB7-43f2-9E16-1651990EB887}"
    TemplateName="STS#0" />
</Elements>

Note that the Id attribute on the <FeatureSiteTemplateAssociation> element is set to the GUID identifier of the feature shown in Listing 9-25 and TemplateName attribute is set to the identifier of the Team Site site definition configuration.

Note that the value of the Scope attribute on the <Feature> element that defines the stapling feature (see Listing 9-27) plays a significant role:

  • If you set the Scope attribute to web, the web event handler will be bound to any Team child site in the site where the stapling feature is activated provided that the Team child site is provisioned after the feature is activated.

  • If you set the Scope attribute to Site, the web event handler will be bound to any Team site in the site collection where the stapling feature is activated provided that the Team child site is provisioned after the feature is activated.

  • If you set the Scope attribute to WebApplication, the web event handler will be bound to any Team site in any site collection in the web application where the stapling feature is activated provided that the Team child site is provisioned after the feature is activated.

  • If you set the Scope attribute to Farm, the web event handler will be bound to any Team site in any site collection in any web application in the farm where the stapling feature is activated provided that the Team child site is provisioned after the feature is activated.

Another important factor is the site definition configuration to which the stapling feature staples your feature. For example, the stapling feature defined in Listing 9-27 staples your feature to the Team Site (STS#0) site definition configuration. Listing 9-29 presents a new version of the element manifest file that associates your feature with the Global site definition configuration (Global#0).

Because regardless of from which specific site definition configuration a site is provisioned, it is also provisioned from the Global site definition configuration:

  • If you set the Scope attribute to web, the web event handler will be bound to any child site in the site where the stapling feature is activated provided that the child site is provisioned after the feature is activated.

  • If you set the Scope attribute to Site, the web event handler will be bound to any site in the site collection where the stapling feature is activated provided that the site is provisioned after the feature is activated.

  • If you set the Scope attribute to WebApplication, the web event handler will be bound to any site in any site collection in the web application where the stapling feature is activated provided that the site is provisioned after the feature is activated.

  • If you set the Scope attribute to Farm, the web event handler will be bound any site in any site collection in any web application in the farm where the stapling feature is activated provided that the site is provisioned after the feature is activated.

As you can see, you can bind the web event handler to any site in any site collection in any web application in a farm by simply setting the Scope attribute to Farm and associating your feature with the Global site definition configuration (GLOBAL#0).

Listing 9-29: The elements.xml file

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <FeatureSiteTemplateAssociation
    Id="{F398089B-CDB7-43f2-9E16-1651990EB887}"
    TemplateName="GLOBAL#0" / >
</Elements>

Binding a Web Event Handler to a New Site Definition Configuration (First Approach)

The preceding section showed you how to use feature stapling to bind your web event handlers to an existing site definition configuration. This section shows you how to bind your web event handlers to a new site definition configuration. There are two ways to do this. The first approach is discussed in this section and the second approach is discussed in the next section.

As discussed earlier, any feature that is referenced from the <SiteFeatures> or <WebFeatures> element of a site definition configuration is automatically activated upon site creation. This is equivalent to running the activatefeature operation of the STSADM command-line utility on the feature. Recall that you implemented a custom site definition configuration named STS2#3. This site definition configuration under the hood uses the STS2#0 site definition configuration (see Listing 7-14 ). Listing 9-30 presents a new version of the portion of the onet.xml file for the STS2 site definition.

Listing 9-30: The onet.xml file for STS2 site definition

<?xml version="1.0" encoding="utf-8"?>
<Project Title="$Resources:onet_TeamWebSite;" Revision="2" ListDir="$Resources:
core,lists_Folder;" xmlns:ows="Microsoft SharePoint" >
    <NavBars>
    ...
    </NavBars>
    <ListTemplates>
    </ListTemplates>
    <DocumentTemplates>
    ...
    </DocumentTemplates>
    <Configurations>
        <Configuration ID="-1" Name="NewWeb" />
        <Configuration ID="0" Name="Default">
            <Modules>
                <Module Name="Default" />
            </Modules>
            <SiteFeatures>
                <!-- BasicWebParts Feature -->
                <Feature ID="00BFEA71-1C5E-4A24-B310-BA51C3EB7A57" />
                <!-- Three-state Workflow Feature -- >
                <Feature ID="FDE5D850-671E-4143-950A-87B473922DC7" />
            </SiteFeatures>
            <WebFeatures>
                <Feature ID="00BFEA71-4EA5-48D4-A4AD-7EA5C011ABE5" />
                <!-- TeamCollab Feature -->
                <Feature ID="F41CC668-37E5-4743-B4A8-74D1DB3FD8A4" />
                <!-- MobilityRedirect -->
                <Feature ID="{1E0CFC51-DD21-4321-9D6B-81FC4B6AEE1A}" />
                <Feature ID="{F398089B-CDB7-43f2-9E16-1651990EB887}" />
            </WebFeatures>
        </Configuration>
            <Configuration ID="1" Name="Blank">
    <Lists />
            <Modules>
                <Module Name="DefaultBlank" />
            </Modules>
            <SiteFeatures>
                <!-- BasicWebParts Feature -->
                <Feature ID="00BFEA71-1C5E-4A24-B310-BA51C3EB7A57" />
                <!-- Three-state Workflow Feature -->
                <Feature ID="FDE5D850-671E-4143-950A-87B473922DC7" />
            </SiteFeatures>
            <WebFeatures>
                <Feature ID="00BFEA71-4EA5-48D4-A4AD-7EA5C011ABE5" />
                <!-- TeamCollab Feature -->
                <Feature ID="F41CC668-37E5-4743-B4A8-74D1DB3FD8A4" />
                <!-- MobilityRedirect -- >
                < Feature ID="{F398089B-CDB7-43f2-9E16-1651990EB887}" / >
            </WebFeatures>
        </Configuration>
            <Configuration ID="2" Name="DWS">
                <Modules>
                    <Module Name="DWS" />
                </Modules>
                <SiteFeatures>
                <!-- BasicWebParts Feature -->
                <Feature ID="00BFEA71-1C5E-4A24-B310-BA51C3EB7A57" />
                <!-- Three-state Workflow Feature -->
                <Feature ID="FDE5D850-671E-4143-950A-87B473922DC7" />
                </SiteFeatures>
            <WebFeatures>
                <Feature ID="00BFEA71-4EA5-48D4-A4AD-7EA5C011ABE5" />
                <!-- TeamCollab Feature -->
                <Feature ID="F41CC668-37E5-4743-B4A8-74D1DB3FD8A4" />
                <!-- MobilityRedirect -->
                <Feature ID="{1E0CFC51-DD21-4321-9D6B-81FC4B6AEE1A}" />
                < Feature ID="{F398089B-CDB7-43f2-9E16-1651990EB887}" />
            </WebFeatures>
        </Configuration>
    </Configurations>
    <Modules>
    ...
    </Modules>
    <ServerEmailFooter> $Resources:ServerEmailFooter; </ServerEmailFooter>
</Project>

As you can see, the boldfaced portions of Listing 9-30 reference the feature shown in Listing 9-25. Recall that this feature binds a web event handler to a site. Because these boldfaced portions, that is, these references to this feature, are included in the <WebFeatures> elements of the Team Site, Blank Site, and Document Spaces site definition configurations, this feature is automatically activated every time a new site is provisioned from one of these site definition configurations. This means that your web event handler will be automatically bound to any site provisioned from one of these site definition configurations.

Binding a Web Event Handler to a New Site Definition Configuration (Second Approach)

If you're implementing a custom site definition, you can use a site provisioning provider to bind your web event handlers to your site definition configurations. This means that your web event handlers will automatically be bound to all sites provisioned form these site definition configurations. Recall that you implemented a custom site definition configuration named STS2#3 and configured it with a site provisioning provider. In this section, you'll add code to this site provisioning provider to bind your web event handler to this site definition configuration.

Listing 9-31 presents the implementation of this custom site provisioning provider.

Listing 9-31: The site provisioning provider

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.Reflection;
namespace ClassLibrary3
{
    public class MySiteProvisioningProvider : SPWebProvisioningProvider
    {
        public override void Provision(SPWebProvisioningProperties props)
        {
            props.Web.ApplyWebTemplate("STS2#0");
            props.Web.Title = props.Data;
            props.Web.Update();
            using (SPSite siteCollection = new SPSite(props.Web.Site.ID))
            {
                using (SPWeb site = siteCollection.OpenWeb(props.Web.ID))
                {
                    site.AllowUnsafeUpdates = true;
                    SPWebApplication app = siteCollection.WebApplication;
                    SPWebConfigModification modification1 = new SPWebConfigModification();
                    modification1.Path = "configuration/appSettings";
                    modification1.Name = "add[@key='MyKey']";
                    modification1.Value = " < add key='MyKey' value='" + props.Web.ID.ToString()
                        + "' / > ";
                    modification1.Owner = Assembly.GetExecutingAssembly().FullName;
                    modification1.Sequence = 100;
                    modification1.Type =
                        SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
                    app.WebConfigModifications.Add(modification1);
                    SPWebService service = SPFarm.Local.Services.GetValue < SPWebService > ();
                    service.ApplyWebConfigModifications();
                    SPEventReceiverDefinition rd = site.EventReceivers.Add();
                    rd.Name = "My Event Receiver";
                    rd.Class = "ClassLibrary3.MyWebEventReceiver2";
                    rd.Assembly = "ClassLibrary3, Version=1.0.0.0, Culture=neutral,
                        PublicKeyToken=df663a77cbaeeca1";
                    rd.Data = "My Event Receiver data";
                    rd.Type = SPEventReceiverType.WebDeleted;
                    rd.Update();
                }
            }
        }
    }
}

As you can see, the Provision method performs these tasks. First, it applies the STS2#0 site definition configuration. Recall that this configuration duplicates the standard STS configuration:

props.Web.ApplyWebTemplate("STS2#0");

Next, it uses the provisioning data specified in the WebTemp*.xml file as the title of the site being provisioned:

props.Web.Title = props.Data;

Then, it commits this change:

props.Web.Update();

Next, it accesses a reference to the SPWeb object that represents the site being provisioned:

using (SPSite siteCollection = new SPSite(props.Web.Site.ID))
{
    using (SPWeb site = siteCollection.OpenWeb(props.Web.ID))
    {

Next, it accesses a reference to the SPWebApplication object that represents the web application in which the site is being provisioned:

SPWebApplication app = siteCollection.WebApplication;

Next, it creates an SPWebConfigModification object to represent the configuration modification that you're about to make. This modification adds an entry to the <appSettings> section of the web.config files of all IIS web sites that map into the web application:

SPWebConfigModification modification1 = new SPWebConfigModification();

Then, it sets the Path property on the SPWebConfigModification object to the XPath expression that selects the parent node, which is <appSettings> in this case:

modification1.Path = "configuration/appSettings";

Next, it sets the Name property on the SPWebConfigModification object to the XPath expression that selects the node you want to add to the <appSettings> parent node:

modification1.Name = "add[@key='MyKey']";

Then, it assigns the actual node being added to the Value property of the SPWebConfigModification object. As you can see, this node is an <add> element that supports two attributes named key and value where the value attribute is set to the GUID identifier of the site being provisioned:

modification1.Value = " < add key='MyKey' value='" + props.Web.ID.ToString() + "'
/ > ";

Next, it specifies EnsureChildNode as the modification type:

modification1.Type =
SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;

Then, it invokes the Add method on the WebConfigModifications collection property of the SPWebApplication object to add the new SPWebConfigModification object to this collection:

app.WebConfigModifications.Add(modification1);

Next, it accesses a reference to the SPWebService object that represents the current web service and invokes the ApplyWebConfigModifications method on this object to add the specified <add> entry to the <appSettings> section of the web.config files of all IIS web sites that map into the web application:

SPWebService service = SPFarm.Local.Services.GetValue < SPWebService > ();
service.ApplyWebConfigModifications();

Then, Provision invokes the Add method on the EventReceivers collection property of the SPWeb object that represents the site being provisioned to instantiate and to return a reference to an SPEventReceiverDefinition object, which will be used to bind your WebDeleted web event handler to the site being provisioned:

SPEventReceiverDefinition rd = site.EventReceivers.Add();

Next, it specifies a unique name for this event receiver definition:

rd.Name = "My Event Receiver";

Then, it specifies the fully namespace-qualified name of the web event receiver that contains your WebDeleted web event handler:

rd.Class = "ClassLibrary3.MyWebEventReceiver2";

Next, it specifies the strong name of the assembly that contains this web event receiver:

rd.Assembly = "ClassLibrary3, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=df663a77cbaeeca1";

Then, it specifies WebDeleted as the web event handler that you want to bind to the site being provisioned:

rd.Type = SPEventReceiverType.WebDeleted;

As you can see, you've moved the code that binds your WebDeleted web event handler inside the Provision method of your site provisioning provider to ensure that this event handler is bound to every site provisioned from your site configuration.

Listing 9-32 presents the implementation of your web event receiver.

Listing 9-32: Your web event receiver

using System;
using Microsoft.SharePoint;
using System.Net.Mail;
using Microsoft.SharePoint.Administration;
using System.Reflection;
namespace ClassLibrary3
{
    public class MyWebEventReceiver2 : SPWebEventReceiver
    {
        public override void WebDeleted(SPWebEventProperties properties)
        {
            properties.Web.AllowUnsafeUpdates = true;
            SPWebApplication app = properties.Web.Site.WebApplication;
            SPWebConfigModification modification1 = new SPWebConfigModification();
            modification1.Path = "configuration/appSettings";
            modification1.Name = "add[@key='MyKey']";
            modification1.Value = " < add key='MyKey' value='" + properties.WebId
                .ToString() + "' / > ";
            modification1.Owner = Assembly.GetExecutingAssembly().FullName;
            modification1.Sequence = 100;
            modification1.Type =
                SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            app.WebConfigModifications.Remove(modification1);
            SPWebService service = SPFarm.Local.Services.GetValue < SPWebService > ();
            service.ApplyWebConfigModifications();
        }
    }
}

As you can see, your WebDeleted web event handler first creates an SPWebConfigModification object and sets its properties to the same exact values as your site provisioning provider. The only difference is that your WebDeleted web event handler invokes the Remove method instead to the Add method to remove the SPWebConfigModification object from the WebConfigModifications collection property of the SPWebApplication object that represents the web application:

app.WebConfigModifications.Remove(modification1);

When your WebDeleted web event handler finally invokes the ApplyWebConfigModifications method on the SPWebService object, the <add> entry that your site provisioning provider added to the <appSettings> configuration section is automatically removed.

In general, you should perform runtime site - specific operations such as adding site - specific entries to the web.config file (recall that the value attribute of the <add> entry is set to the GUID identifier of the site, which is a site-specific value, which you can access only at runtime) inside the Provision method of your site provisioning provider. You should also bind your web event handlers from within the Provision method as well. Your WebDeleted web event handler must clean up resources such as web.config when the site is deleted. For example, the site-specific <add> entry should be removed when the respective site is deleted.

This chapter covered the three types of event receivers, including list, item, and web event receivers. You saw examples of these three event receiver types and learned how to bind them to the appropriate SharePoint entities.

Chapter 10 moves on to SharePoint Web parts, where you learn how to implement your own custom Web parts and deploy them to SharePoint.

Previous part: Chapter 9: Event Receivers Part 1 of 2 (Expert WSS 3.0 and MOSS 2007 Programming)

Show: