How to add a Feature Receiver to a Feature


Table of Contents

Feature Receivers allow programmatic code to be run using methods overriding the SPFeatureReceiver interface:

  • on activating a feature in a Farm, Web Application, Site Collection, Site (FeatureActivated)
  • on deactivating a feature in a Farm, Web Application, Site Collection, Site (FeatureDeactivating)
  • on installing a feature to a farm (FeatureInstalled)
  • on unintslling a feature from a farm (FeatureUninstalling)

This has great power in terms of setting up required elements for a feature and also cleaning up afterwards if it is deactivated or uninstalled.

SPFeatureReceiverProperties

Each method has a SPFeatureReceiverProperties object passed through to it with various properties on it. These can be used to get the context of the Feature and also to inspect the features state.

How to add a Feature Receiver to a feature

Creating the feature receiver class

You are going to need a feature receiver class, typically this is part of the Visual Studio project that contains the Feature Receiver. Most SharePoint Productivity tools (WSPBuilder, STSDev, VSeWSS) will create you a Feature Receiver with a feature receiver class already hooked up.

This class will look something like this when first created:

using System;
using Microsoft.SharePoint;

namespace WSPBuilderProject1
{
    class FeatureWithReceiver1 : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}

If you are not going do anything on installing and uninstalling simply remove throwing the exceptions as this will effect performance.
We are going to change the title of the site activating the feature and change it back on deactivating it as a quick example. Note that this feature is scoped at 'Web' level and therefore the properties.Feature.Parent will be an SPWeb type object. See Gotcha1.

 
using System;
using Microsoft.SharePoint;

namespace WSPBuilderProject1
{
    class FeatureWithReceiver1 : SPFeatureReceiver
    {
        private const string _enhancedTitle = " Enhanced Version";
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWeb web = (SPWeb) properties.Feature.Parent;
            web.Title = web.Title + _enhancedTitle; 
            web.Update();
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWeb web = (SPWeb)properties.Feature.Parent;
            web.Title = web.Title.Replace(_enhancedTitle, string.Empty);
            web.Update();
        }

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {}

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {}
    }
}

Hooking the feature receiver class to the feature

Creating the class and compiling the dll and putting it in the WSP package is not enough. You need to associate the feature with the class. This in done in the Feature XML, you will notice there is now a ReceiverAssembly and ReceiverClass attribute on the Feature element. The values for these attributes can be found by following How to build the assembly reference used in Feature files and SafeControls entries with class, assembly name and public key token.

<?xml version="1.0" encoding="utf-8"?>
<Feature  Id="6eec15cf-5505-430f-a13e-c8ad96140c2f"
          Title="FeatureWithReceiver1"
          Description="Description for FeatureWithReceiver1"
          Version="12.0.0.0"
          Hidden="FALSE"
          Scope="Web"
          DefaultResourceFile="core"
          ReceiverAssembly="WSPBuilderProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=be02c50325b391ed"
          ReceiverClass="WSPBuilderProject1.FeatureWithReceiver1"
          xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

Debugging Feature Receivers

You can debug feature receivers by attaching the debugger to the w3wp.exe process of your web application you are testing. For more on this see SharePoint Development Debugging

Feature Receiver Gotchas

Context when activating feature using STSADM

Source chakkaradeep

There are multiple ways of getting the context of the SPWeb object:

public override void 
    FeatureActivated(SPFeatureReceiverProperties properties)
{
   SPWeb CurWeb = SPContext.Current.Web;
   // Rest of the code follows....
}

If you activate the feature via STSADM you will get an error: 'Object reference not set to an instance of the object'. This will work if you activate the feature via the web UI. The reason for this is that the cmd.exe process does not understand or know how to get your current context! If you are running via browser, you have the w3wp.exe process from which you can get the context!

You will also get errors if you activate a feature that is scoped at 'Site' level and cast the properties.Feature.Parent to SPWeb instead of SPSite. The below code will also help manage this for you.

Below is an extension method for you! (Matt Smith's blog)

public static class Extensions
{
    /// <summary>
    /// Gets the web.
    /// </summary>
    /// <param name="properties">The properties.</param>
    /// <returns></returns>
    public static SPWeb GetWeb(this SPFeatureReceiverProperties properties)
    {
        SPWeb site;
        if (properties.Feature.Parent is SPWeb)
        {
            site = (SPWeb)properties.Feature.Parent;
        }
        else if (properties.Feature.Parent is SPSite)
        {
            site = ((SPSite)properties.Feature.Parent).RootWeb;
        }
        else
        {
            throw new Exception("Unable to retrieve SPWeb - 
                           this feature is not Site or Web-scoped.");
        }
        return site;
    }
}

Depending on the scope of your feature, you cast the Parent.

And you use it like this:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    SPWeb CurWeb = properties.GetWeb();
    Guid listId = CurWeb
                  .Lists
                  .Add(ListTitle, ListDescription, SPListTemplateType.GenericList);
    CurWeb.Update();
}
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.


  1. Sep 11, 2009

    Anonymous says:

    Thanks Jeremy for this brilliant article. I have a problem with getting it to w...

    Thanks Jeremy for this brilliant article.

    I have a problem with getting it to work with some MOSS out-of-the-box sites. It works perfectly for the Wiki, Blog, Basic Meeting Workspace, Record Centre, Team Site, Blank Site, and Document Workspace sites, but generates an error message for the Report Centre, Search Centre with Tabs, Site Directory, My Site Host, Collaboration portal, and Publishing portal sites. All the sites I am testting on are newly created.

    Error messages:
    **********
    For the "Report Center" site the error message is:

    Object reference not set to an instance of an object. at
    TopMenu.TopMenuBar.FeatureActivated(SPFeatureReceiverProperties properties)
    at Microsoft.SharePoint.SPFeature.DoActivationCallout(Boolean fActivate,
    Boolean fForce)
    at Microsoft.SharePoint.SPFeature.Activate(SPSite siteParent, SPWeb
    webParent, SPFeaturePropertyCollection props, Boolean fForce)
    at Microsoft.SharePoint.SPFeatureCollection.AddInternal(Guid featureId,
    SPFeaturePropertyCollection properties, Boolean force, Boolean fMarkOnly)
    at Microsoft.SharePoint.SPFeatureCollection.Add(Guid featureId)
    at
    Microsoft.SharePoint.WebControls.FeatureActivator.BtnActivateFeature_Click(Object
    objSender, EventArgs evtargs)
    at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
    at System.Web.UI.WebControls.Button.RaisePostBackEvent(String
    eventArgument)
    at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler
    sourceControl, String eventArgument)
    at System.Web.UI.Page.ProcessRequestMain(Boolean
    includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
    **********

    For the Search Centre with Tabs, Site Directory, My Site Host, Collaboration portal, and Publishing portal sites the error message is:

    Specified argument was out of the range of valid values. at
    Microsoft.SharePoint.Navigation.SPNavigationNodeCollection.get_Item(Int32
    index)
    at TopMenu.TopMenuBar.FeatureActivated(SPFeatureReceiverProperties
    properties)
    at Microsoft.SharePoint.SPFeature.DoActivationCallout(Boolean fActivate,
    Boolean fForce)
    at Microsoft.SharePoint.SPFeature.Activate(SPSite siteParent, SPWeb
    webParent, SPFeaturePropertyCollection props, Boolean fForce)
    at Microsoft.SharePoint.SPFeatureCollection.AddInternal(Guid featureId,
    SPFeaturePropertyCollection properties, Boolean force, Boolean fMarkOnly)
    at Microsoft.SharePoint.SPFeatureCollection.Add(Guid featureId)
    at
    Microsoft.SharePoint.WebControls.FeatureActivator.BtnActivateFeature_Click(Object
    objSender, EventArgs evtargs)
    at System.Web.UI.WebControls.Button.OnClick(EventArgs e)
    at System.Web.UI.WebControls.Button.RaisePostBackEvent(String
    eventArgument)
    at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler
    sourceControl, String eventArgument)
    at System.Web.UI.Page.ProcessRequestMain(Boolean
    includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
    *************

    My Feature Receiver code:
    ************************************
    using System;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;
    using Microsoft.SharePoint.WebPartPages;
    using Microsoft.SharePoint.Navigation;
    using Microsoft.SharePoint.Administration;

    namespace TopMenu
    {

    class TopMenuBar : SPFeatureReceiver
    {
    public override void FeatureActivated(SPFeatureReceiverProperties
    properties)
    {
    SPWeb site = properties.GetWeb();
    SPNavigationNodeCollection topNav =
    site.Navigation.TopNavigationBar;
    // create top navigation tab & add to top nav bar
    SPNavigationNode topNavMenuItem = new SPNavigationNode("Top Menu
    Site Scope", string.Empty, false);
    topNav[0].Children.AddAsLast(topNavMenuItem);

    // create first drop down pointing to Master Page Gallery
    SPNavigationNode mpg = new SPNavigationNode("Master Page Gallery
    TEST 777", string.Format("

    Unknown macro: {0}

    /_catalogs/masterpage", site.Url), true);
    topNavMenuItem.Children.AddAsFirst(mpg);

    // create first drop down pointing to Web Part Gallery
    SPNavigationNode wpg = new SPNavigationNode("Web Part Gallery",
    string.Format("

    /_catalogs/wp", site.Url), true);
    topNavMenuItem.Children.AddAsLast(wpg);

    // create first drop down pointing to List Template Gallery
    SPNavigationNode ltg = new SPNavigationNode("List Template
    Gallery", string.Format("

    Unknown macro: {0}

    /_catalogs/lt", site.Url), true);
    topNavMenuItem.Children.AddAsLast(ltg);

    // create first drop down pointing to Site Template Gallery
    SPNavigationNode stg = new SPNavigationNode("Site Template
    Gallery", string.Format("

    /_catalogs/wt", site.Url), true);
    topNavMenuItem.Children.AddAsLast(stg);

    // commit the changes
    site.Update();

    }

    public override void FeatureDeactivating(SPFeatureReceiverProperties
    properties)
    {
    SPWeb site = properties.GetWeb();

    // delete folder of site pages provisioned during activation
    SPFolder sitePagesFolder = site.GetFolder("TopMenuBar");
    sitePagesFolder.Delete();

    SPNavigationNodeCollection topNav =
    site.Navigation.TopNavigationBar;

    for (int i = topNav[0].Children.Count - 1; i >= 0; i--)
    {
    if (
    topNav[0].Children[i].Title == "Top Menu Site Scope"
    //|| topNav[0].Children[i].Title == "ASP Examples 2"
    )

    Unknown macro: { // delete node topNav[0].Children[i].Delete(); }

    }

    }

    public override void FeatureInstalled(SPFeatureReceiverProperties
    properties)

    Unknown macro: { /* no op */ }

    public override void FeatureUninstalling(SPFeatureReceiverProperties
    properties)

    }

    // Add this class so this project is interchangable between
    // SPWeb or SPSite
    public static class Extensions
    {
    /// <summary>

    /// Gets the web.

    /// </summary>
    /// <param name="properties">The properties.</param>

    /// <returns></returns>

    public static SPWeb GetWeb(this SPFeatureReceiverProperties
    properties)
    {
    SPWeb site;
    if (properties.Feature.Parent is SPWeb)

    Unknown macro: { site = (SPWeb)properties.Feature.Parent; }

    else if (properties.Feature.Parent is SPSite)

    Unknown macro: { site = ((SPSite)properties.Feature.Parent).RootWeb; }

    else

    Unknown macro: { throw new Exception("Unable to retrieve SPWeb - this feature is not Site or Web-scoped."); }

    return site;
    }
    }
    }
    ************************************

    I would like to get this Feature Receiver working for the "Report Centre" site, but cannot understand why it works perfectly for one site but not another. Do I need to disable some default features to get it work within the Report Centre?

    Sorry for this large message but I wanted to put as much info as possible so you maybe able to solve this problem.

    Do you know what I am doing wrong? Many thanks
    Alan


Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License. Hosted generously by CustomWare