04 December, 2006

Understanding SharePoint Feature Receivers

In this post I’m going to try to explain what a Feature Receiver is in SharePoint 2007 and show you an example of how to use it. First things first.

What is a SharePoint Feature

A feature is kinda tricky to explain. It’s a functional unit that can be activated and deactivated in different scopes (site collection, web etc). There is a bunch of features out of the box as seen below.

List of Features

Problem definition

In a recent project we decided to deploy a big chunk of meta data columns (site columns) as a feature. A couple of these columns where lookup columns and should always get their data from lists that are present in the root site of the collection.

When defining site columns as you can add a ListId attribute, but in order to go cross-site for lookup you need to enter a WebId also. The xsd schema for features does not allow this. The schema can be found in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\wss.xsd. Here’s an example of how a lookup column can look when defined in our fields.xml, as you can see no WebId.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Field ID="{d0809abe-9943-4502-a372-07bcba109Ec0}"
        Name="CostCenter"
        Group="My Columns"
        Type="Lookup"
        List="{d9bc9824-4c3a-472a-9e15-e89a2977d477}"
        ShowField="Title"
        DisplayName="Cost Center"
        SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
        StaticName="CostCenter"
        Required="TRUE"
        UnlimitedLengthInDocumentLibrary="FALSE"
        ColName="int1"
        RowOrdinal="0"
        Description="Cost Center for the unit">
    </Field>
</Elements>

Solution

Well maybe you guessed it by reading the title of this post. The way I solved it was by using a Feature Receiver. Once again, let’s start from the top.

What is a SharePoint Feature Receiver

It’s a way of acting on events that happens to a Feature. These events are:

Method Description
Installed When a feature has been installed on the server / farm
Uninstalling Before a feature is uninstalled from the server/farm
Activated When a feature is activated for it’s scope
Deactivating Before a feature is deactivated for it’s scope

So in the case of this example it’s the activated scenario we’re interested in. Every time our feature is activated we’re going to hot wire all lookup-columns to point to the WebId of the root-web of the site collection

To create a Feature Receiver in Visual Studio create a class library project, add a reference to the Windows.SharePoint.Services assembly. Create new class (in this example I will call it SiteColumnFeatureReceiver) and make it inherit from SPFeatureReceiver. Now when we have the pluming done we’ll override the FeatureActivated method. Then we’re going to loop over all columns and find the ones that are lookup-columns and hook them up to the root-web. So, this is what it can look like:

using System;
using System.Globalization;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace MyAssembly.MyFeatures
{
   public class SiteColumnsFeatureReciver : SPFeatureReceiver
   {
      private const string CONST_LOOKUP = "Lookup";
      private const string CONST_FIELD = "Field";

      public override void FeatureActivated(SPFeatureReceiverProperties properties)
      {
         SPWeb site = properties.Feature.Parent as SPWeb;
         string webid;
         SPElementDefinitionCollection elementDefinitionCollection =
            properties.Definition.GetElementDefinitions(new CultureInfo(1033));
         foreach (SPElementDefinition elementDefinition in elementDefinitionCollection)
         {
            if (elementDefinition.ElementType == CONST_FIELD)
            {
               XmlNode node = elementDefinition.XmlDefinition;
               if (node.Attributes["Type"].Value == CONST_LOOKUP)
               {
                  try
                  {
                     SPSite parent = properties.Feature.Parent as SPSite;
                     if (parent != null)
                     {
                        SPWeb web = parent.RootWeb;
                        webid = web.ID.ToString("D");
                        string fieldId = node.Attributes["ID"].Value;
                        SPFieldLookup lookup = (SPFieldLookup) web.Fields[new Guid(fieldId)];
                        lookup.LookupWebId = new Guid(webid);
                        lookup.Update(true);
                     }
                  }
                  catch (Exception e)
                  {
                     Console.WriteLine(e);
                  }
               }
            }
         }
      }

      public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
      { }

      public override void FeatureInstalled(SPFeatureReceiverProperties properties)
      { }

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

Make sure you sign the assembly then compile it and we have a Featere Receiver! Next step: Installation.

Deploy the assembly we just created to the GAC (either by copying it to C:\WINDOWS\Assembly or by using GacUtil.exe).

In our Feature.xml file that defines things like name, scope, included elements and such we have to hook up the newly created feature receiver.

<Feature Id="0916E74F-D036-40c1-8485-C8CDCEF028F2"
   Title="My Fields Definition"
   Description="Site columns added by Niklas"
   Version="1.0.0.0"
   Scope="Site"
   Hidden="FALSE"
   xmlns="http://schemas.microsoft.com/sharepoint/"
   ReceiverAssembly="MyAssambly.MyFeatures, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5534820fb2bf9b92"
   ReceiverClass="MyAssambly.MyFeatures.SiteColumnsFeatureReciver" >
   <ElementManifests>
      <ElementManifest Location="fields.xml" />
   </ElementManifests>
</Feature>

The properties of importance in this regard is ofcource ReceiverAssebmly and ReceiverClass.

Now, copy we need to copy all our feature xml-files to C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\SiteColumns. And let’s install the feature.

stsadm -o insatllfeature -name SiteColumns

Now, every time someone hits the Activate button for our feature (or runs the stsadm -o activatefeature command) our code will be executed. Now in this example I only set the WebId of the lookup columns but you could ofcource do anything within the limits of the object model. For example create a few document library’s with a certain structure…


Tags: ,