Select Page

Develop and deploy a SharePoint Event Receiver from the scratch

Develop and deploy a SharePoint Event Receiver from the scratch

There are many information peaces on the internet how to build a decent working event receiver in SharePoint – mainly good, but still, only peaces. I’ll try to present here a complete and quick “how to” guide  – from making decision which event we want to catch until the event deployment.

What you will not find here is a description of developing event handlers using Windows SharePoint Services 3.0 Tools: Visual Studio 2008 Extensions – you cannot use them in x64 environments, and “Null reference exception” by deployment pops up much too often for my taste. Well, we are all waiting for 1.3 version, but until then…

Events

SharePoint is quite rich with the events it exposes. We can categorize SharePoint events in two different categories: by the “level” which fires the event (site, list, item), and by the type of the event (synchronous and asynchronous).

What means synchronous  / asynchronous?

  • Synchronous: happens ‘before’ the actual event, you have the HttpContext and you can show an error message in the browser and cancel the event.
  • Asynchronous: happens ‘after’ the actual event, there’s no HttpContext  and you cannot directly show an error message or cancel the event – you can handle what happens after the event is fired.

As an example of synchronous and asynchronous events we can take item-level events “ItemAdding” and “ItemAdded” – by handling of “ItemAdding” we can take a look what that item is, and, if necessary, cancel the adding and display the error message in the SharePoint page to the user. When handling “ItemAdded”, we know that the item is already in the SharePoint list, but we can start some post-add actions with that item.

Complete table of SharePoint events you can find in my previous post.

In this example, we are going to create a simple receiver, which will handle two Asynchronous events (ItemAdded and ItemUpdated) in all Document and Picture libraries at one SharePoint web site, and deploy it as a web site – level feature.

In the code which handles these two events, we are then going to break permissions inheritance for given item and to set the custom item-level based permissions.

Defining a feature for our event receiver

We want to deploy our event receiver as a feature, so, first of all, we have to create a Feature Folder in the Features setup directory  (Local_Drive:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURES). Let’s say, our feature will be named “Set Items Permissions Event Handler”, so we name our Feature Folder as “PermissionsEventHandler”.

A Feature folder (PermissionsEventHandler) may contain only a Feature.xml file (which describes our feature), or it may contain a Feature.xml file and any number of supporting element files. In our case, Feature folder will, beside the Feature.xml, contain only Elements.xml file, which will describe which events we want to receive and handle.

  • Feature.xml – .xml file that defines the base properties of the Feature and lists elements bound to it, such as XML files containing element manifests and any other supporting files
  • Elements.xml – a “manifest file” -  .xml file that contains definitions to the feature’s elements. This manifest file is referenced from Feature.xml. In our case, it will contain events we want to receive and handle

So, we have made the “PermissionsEventHandler”  folder, and our Feature.xml and Elements.xml files in it. Let’s look at the structure of the both XML files:

Feature.xml
   1: <Feature Scope="Web"

   2:     Title="Set Items Permissions Event Handler"

   3:     Description = "Sets the item permissions immediately upon item is added or edited"

   4:     Id="98D6B8CB-1C6A-469c-9119-717AEBE68515"

   5:     xmlns="http://schemas.microsoft.com/sharepoint/">

   6:         <ElementManifests>

   7:             <ElementManifest Location="Elements.xml"/>

   8:         </ElementManifests>

   9:         <Properties>  

  10:             <Property Key="OwnersGroupName" Value="MySiteOwners"/>

  11:             <Property Key="VisitorsGroupName" Value="MySiteVisitors"/>  

  12:             <Property Key="ContributorsGroupName" Value="MySiteContributors"/>  

  13:         </Properties>  

  14: </Feature>

In the first line, we define the feature scope, it’s a kind of the feature visibility: from which hierarchy level will our feature be accessible. We want to have this feature only at one SharePoint Web site, so our scope is going to be Web, but it can vary between Farm (SharePoint Farm), WebApplication (SharePoint application), Site (site collection), and Web (SharePoint web site). Here you can find out more about scopes.

In the lines 2-5, we see the feature name, description and Guid. We create the feature guid ourselves, and it will serve as the unique identifier for this feature which we will use later.

In the lines 6-8 we create a reference to the Elements.xml manifest file, which will define events we want to handle.

In the lines 9-13 we can define Feature Properties, you can consider it as kind of the string resources for our feature – they are actually key/value pairs. In this case we have stored the names of the SharePoint groups to which we are going to give permissions to this item after we break inheritance, so that we don’t have to hard code these names in our source code.

Elements.xml

Our event receiver has to catch the following events: ItemAdded and ItemUpdated at all the Document Libraries and Picture libraries on the SharePoint site on which the feature has been activated.

This is exactly what we have to describe in the Elements.xml file of our feature: which events we hook onto, and which types of the lists/libraries we want to observe.

We know the events (ItemAdded, ItemUpdated), we still need to know the codes for the lists and libraries we want to “observe”. In our case 101 is a standard document library, and 109 is a standard picture library. Full list of the list and library types you can find here.

So, let’s look at the Elements.xml with necessary definitions:

   1: <Elements xmlns="http://schemas.microsoft.com/sharepoint/">

   2:   <Receivers ListTemplateId="101">

   3:     <Receiver>

   4:       <Name>DocLibraryPermissionsEventHandlerOnAdded</Name>

   5:       <Type>ItemAdded</Type>

   6:       <SequenceNumber>10000</SequenceNumber>

   7:       <Assembly>PermissionsEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

   8:       <Class>PermissionsEventHandler.PermissionsHandler</Class>

   9:       <Data></Data>

  10:        <Filter></Filter>

  11:      </Receiver>

  12:      <Receiver>

  13:        <Name>DocLibraryPermissionsEventHandlerOnUpdated</Name>

  14:        <Type>ItemUpdated</Type>

  15:        <SequenceNumber>10001</SequenceNumber>

  16:        <Assembly>PermissionsEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

  17:        <Class>PermissionsEventHandler.PermissionsHandler</Class>

  18:        <Data></Data>

  19:        <Filter></Filter>

  20:      </Receiver>

  21:    </Receivers>

  22:    <Receivers ListTemplateId="109">

  23:      <Receiver>

  24:        <Name>PictureLibraryPermissionsEventHandlerOnAdded</Name>

  25:        <Type>ItemAdded</Type>

  26:        <SequenceNumber>10000</SequenceNumber>

  27:        <Assembly>PermissionsEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

  28:        <Class>PermissionsEventHandler.PermissionsHandler</Class>

  29:        <Data></Data>

  30:        <Filter></Filter>

  31:      </Receiver>

  32:      <Receiver>

  33:        <Name>PictureLibraryPermissionsEventHandlerOnUpdated</Name>

  34:        <Type>ItemUpdated</Type>

  35:        <SequenceNumber>10001</SequenceNumber>

  36:        <Assembly>PermissionsEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

  37:        <Class>PermissionsEventHandler.PermissionsHandler</Class>

  38:        <Data></Data>

  39:        <Filter></Filter>

  40:      </Receiver>

  41:    </Receivers>

  42: </Elements>

So, what do we have here?

The first-level node is “Receivers”, where we define the list types we want to hook on – we have only two Receivers nodes, with the attributes ListTemplateId with values 101 (Document Library) and 109 (Picture library).

Now, inside each “Receivers” node, we define “Receiver” nodes with the events we want to hook onto: in our both “Receivers”, we have two “Receiver” nodes – with types “ItemAdded” and “ItemUpdated”.

Each Receivers node has following child nodes:

  • Name – unique name for the receiver – you can set it upon your wish
  • Type – is actually the event name from the events table
  • SequenceNumber – integer which determines in which order the event receivers will be fired
  • Assembly – our assembly (DLL) which actually contains event landler. Assembly will be deployed in GAC.
  • Class – class in our assembly which contains item receivers

The tricky part here are Assembly and Class tags – we do not have them yet, so we’ll return later to fill it in.

OK, the feature for our event receiver is (almost) ready, let’s finally start writing some code!

Developing an Event Receiver

Let’s look now on the event receiver code itself.

Since we are going to user Microsoft.SharePoint namespace – we have to develop directly on the MOSS server, there is (unfortunately) no way around.

We have to create a “Class Library” project, in our case with the “PermissionsEventHandler” name, and to set that the namespace for the project is as well “PermissionsEventHandler”.

Please, sign the project immediately, since SharePoint will reject to execute the feature if it does not have a strong name,

Add a new reference in our project to the Microsoft.SharePoint.dll – you will have to browse for the file, it is placed in the 12 Hives ISAPI folder:

LocalDrive:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12ISAPIMicrosoft.SharePoint.dll

Now, create a Class in the project, with the name “PermissionsHandler”, which inherits “SPItemEventReceiver” class, because both of the events we want to handle are methods of the SPItemEventReciever class.

Let’s look at a full class code:

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Linq;

   4: using System.Text;

   5: using Microsoft.SharePoint;

   6: using System.Diagnostics;

   7: using System.Configuration;

   8:  

   9: namespace PermissionsEventHandler

  10: {

  11:     public class PermissionsHandler : SPItemEventReceiver

  12:     {

  13:  

  14:         /// <summary>

  15:         /// Update item permissions when item has been added

  16:         /// </summary>

  17:         /// <param name="properties">Item event properties</param>

  18:         public override void ItemAdded(SPItemEventProperties properties)

  19:         {

  20:             updateItemPermissions(properties);

  21:         }

  22:  

  23:         /// <summary>

  24:         /// Update item permissions when item has been updated

  25:         /// </summary>

  26:         /// <param name="properties">Item event properties</param>

  27:         public override void ItemUpdated(SPItemEventProperties properties)

  28:         {

  29:             updateItemPermissions(properties);

  30:         }

  31:  

  32:  

  33:         /// <summary>

  34:         /// Updates permissions on the library items

  35:         /// </summary>

  36:         /// <param name="properties">event properties</param>

  37:         private void updateItemPermissions(SPItemEventProperties properties)

  38:         {

  39:             try

  40:             {

  41:                 //temporarely disable item firing so that we do not end in the infinite loop

  42:                 this.DisableEventFiring();

  43:  

  44:                 //get the item itself on which the event was fired

  45:                 SPListItem sharepointItem = properties.ListItem;

  46:  

  47:                 //

  48:                 //now, get the same item with the elevated privileges 

  49:                 //we have to do it that way because we do not know which level of

  50:                 //permissions the current user has

  51:                 SPSecurity.RunWithElevatedPrivileges(delegate()

  52:                 {

  53:                     SPSite elevatedSite = new SPSite(sharepointItem.ParentList.parentWeb.Site.ID);

  54:                     SPWeb elevatedWeb = elevatedSite.OpenWeb(sharepointItem.ParentList.ParentWeb.ID);

  55:                     SPList elevatedList = elevatedWeb.Lists[parentList.ID];

  56:  

  57:                     //get the file with the privileged permissions

  58:                     SPListItem elevatedItem = elevatedList.Items.GetItemById(properties.ListItem.ID);

  59:  

  60:                     //

  61:                     //get the SharePoint group names from the feature configuration (Frature.xml file)

  62:                     SPFeature feature = elevatedWeb.Features[new Guid("98D6B8CB-1C6A-469c-9119-717AEBE68515")];

  63:                     

  64:                     string ownersGroupName = feature.Properties["OwnersGroupName"].Value;

  65:                     string visitorsGroupName = feature.Properties["VisitorsGroupName"].Value;

  66:                     string contributorsGroupName = feature.Properties["MySiteContributors"].Value;

  67:  

  68:                     //

  69:                     //do your logic here...

  70:                     //

  71:                     //

  72:                     // break item permissions inheritance and assign new permissions based on 

  73:                     // the values of the item meta properties

  74:  

  75:                 });

  76:  

  77:                 //enable the event firing again

  78:                 this.EnableEventFiring();

  79:             }

  80:             catch (Exception ex)

  81:             {

  82:                 Trace.WriteLine(ex.Message);

  83:             }

  84:  

  85:         }

  86:     }

  87: }

We’ll first override our both event handlers (ItemAdded and ItemUpdated). As a parameter in the handler, we get an object of the SPItemEventProperties type – it contains an SPListItem object which is actually reference to our item.

Since we want the same logic in both our events, we call the “updateItemPermissions” method from both event handlers.

Note that we need to avoid the infinite loop: in the updateItemPermissions method, the first code line is this.DisableEventFiring() method call. It basically does what it says: stops firing this event again – we have to do that not to end up in the infinite loop, as the event would call itself (because we DO change our item in our event handler code). Of course, at the end, in the finally block, we have to call this.EnableEventFiring().

Further in the code, we get the same SPListItem (item we have added/updated) with the elevated permissions (maybe the user who added item actually do not have rights to change permissions, but they still have to be changed?!)

In the lines 61-66 there is an example for reading Feature properties from the Feature.xml file – in this example we just read the group names (explained with the Features.Xml file) .

Update Elements.Xml with the class library information

Compile the class library and copy the DLL manualy to the GAC (Global Assembly Cache) (c:windowsassembly). Now, open GAC in the Windows Explorer, find library’s token (by right-clicking properties on library), and copy it to the Elements.xml in the PublicKeyToken part of the Assembly tag – as often as Assembly tag appears.

topkenproperties_thumb_44DB68C2


You will also have to update Namespace and Class name of the Assembly and Class tags in the Elements.xml, it basically has to have this structure:

   1: <Assembly>NameSpace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

   2: <Class>NameSpace.ClassName</Class>

in our example, it is going to be:

   1: <Assembly>PermissionsEventHandler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=67d615643aea660a</Assembly>

   2: <Class>PermissionsEventHandler.PermissionsHandler</Class>

 

Deploying the event receiver feature

Since there is often a need to adjust Feature.Xml and Manifest.xml, i tend to have primary version of both Xml files in the solution directory, and, to deploy (copy) them to the SharePoint features directory within the deployment process.

A current batch file can come pretty handy with deployment:

deployfeature.bat
   1: gacutil /i "..bindebugPermissionsEventHandler.dll"

   2:  

   3: copy feature.xml "C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESProjectDocumentPermissionsfeature.xml"

   4:  

   5: copy elements.xml "C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESProjectDocumentPermissionselements.xml"

   6:  

   7: stsadm -o installfeature -filename PermissionsEventHandlerFeature.xml

   8: rem pause

   9:  

  10: stsadm -o activatefeature -filename PermissionsEventHandlerFeature.xml -url http://myserver/mysite/

  11:  

  12: iisreset

  13:  

  14: echo "Done"

The file is pretty self-explanatory: we first have to deploy our class library to the Global Assembly Cache, copy “local” copy of the Feature.Xml and Elements.Xml files from Visual Studio solution to the SharePoint features directory, Install the feature using the STSADMIN command tool, and activate feature on our web site (http://myserver/mysite/)  – as well by using STSADMIN. And the necessary IISRESET.

Sometimes, you’ll need to undeploy the feature as well – and in the development process you’ll do it as often as deploying the feature, believe me, and the following batch file can come handy then.

undelpoyfeature.bat
   1: stsadm -o deactivatefeature -filename PermissionsEventHandlerFeature.xml -url http://myserver/mysite/

   2:  

   3: stsadm -o uninstallfeature -filename PermissionsEventHandlerFeature.xml -force

   4:  

   5: del "C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESPermissionsEventHandlerfeature.xml"

   6:  

   7: del "C:Program FilesCommon FilesMicrosoft Sharedweb server extensions12TEMPLATEFEATURESPermissionsEventHandlerelements.xml"

   8:  

   9: gacutil /uf PermissionsEventHandler

  10:  

  11: iisreset

  12:  

  13: echo "Done"

 

Debug the feature


Debugging is kind of important when developing a new application – in this case event receiver. As a difference to the Windows Forms and Web applications, we cannot start our application here with F5: we need to set the break point somewhere, and wait until someone adds or edits a document.

Furthermore, our DLL is in GAC. That means, that, for debugging, our PDB (symbols database) has to be in GAC as well. Since you can only add DLLs in GAC, there is no straight way how to do that. A simple trick which always works is to substitute GAC directory (C:WindowsAssembly) to a new drive letter (in command prompt):

SUBST G: C:WindowsAssembly

Now, if we open a “drive” “G” in Windows Explorer, we will se a real structure of the GAC folder, not the one which is usually displayed when looking to c:windowsassembly.

Now, we have to find a class library folder (it usually has the format c:GAC_MSILClassLibraryNameversion__token), and to copy our PDB in that folder.

gacfolder_thumb_4B8E7245

When we have done that, we have to attach our Visual Studio debugger to the IIS process where our SharePoint is executed. To do that, with your event handler solution still open, go to the Debug->Attach menu and select w3wp.exe process.

attach_725C8885

And now, you can set the break point in your code and wait that somebody adds the file.

I hope this helps someone 🙂

Previous

Next