Registering EventHandlers against ContentTypes

In SharePoint, Event Handlers (or Event Receivers – whichever terminology, a child of SPItemEventReceiver) can be registered against lists/libraries. You can do this through an SPWeb (site) scoped feature, declaratively or programmatically with an SPEventReceiverDefinition.

Unfortunately, you can’t declaratively register event receivers at the SPSite (Site collection) level – which would be fantastic – just turn on the feature and across all your site collection lists/libraries of a give type would get additional event receivers. Personally, I think this may be a bug; I don’t see why this shouldn’t work at the SPSite level.

Anyway, there is still another option – we could register our event against a particular Content Type. This is a less often used approach, which raises two questions – how do you do it, and what happens to child Content Types?

So, how do you register the event? Well, first off, you can’t do it declaratively – CAML won’t let you register against a Content Type. That seems a bit of an omission. Still, we still have the programmatic option through SharePoint’s API – something like:

private void RegisterEventHandler(SPContentType ct, string name,
             int sequence, string evAssembly, string evClass, SPEventReceiverType type)
{
    SPEventReceiverDefinition oEv = ct.EventReceivers.Add();
    oEv.Name = name;
    oEv.Type = type;
    oEv.SequenceNumber = sequence;
    oEv.Assembly = evAssembly;
    oEv.Class = evClass;
    oEv.Data = string.Empty;
    oEv.Update();
}

Great! But what runs this? Well, we’ll want to use a Feature, and we can’t add it declaratively – so I’ll use a Feature Receiver (SPFeatureReceiver). When the feature is activated/deactivated, it’ll run some code, and we’ll add/remove our event receiver. The code I come up with was


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;

namespace EventReceiverTest
{
    class AWBRegisterEventHandler : SPFeatureReceiver
    {
        private string[] cts = new string[] { "0x0101" };

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {

            foreach (string ct in cts)
            {
                try
                {
                    SPContentTypeId ctId = new SPContentTypeId(ct);
                    SPSite site = properties.Feature.Parent as SPSite;

                    if (site == null)
                    {
                        throw new ArgumentException("Feature must be activated at an SPSite level");
                    }

                    SPContentType currentCT = site.RootWeb.ContentTypes[ctId];

                    if (currentCT == null)
                    {
                        throw new ArgumentException(string.Format("Content Type ID {0} not found"));
                    }

      string evClass = typeof(AWBEventHandler).FullName;
      string evAssembly = typeof(AWBEventHandler).Assembly.FullName;

                    RegisterEventHandler(currentCT,"Content Type Event Receiver", 1000, evAssembly, evClass, SPEventReceiverType.ItemAdding);
                    RegisterEventHandler(currentCT,"Content Type Event Receiver", 1000, evAssembly, evClass, SPEventReceiverType.ItemAdded);
                    RegisterEventHandler(currentCT,"Content Type Event Receiver", 1000, evAssembly, evClass, SPEventReceiverType.ItemUpdating);
                    RegisterEventHandler(currentCT,"Content Type Event Receiver", 1000, evAssembly, evClass, SPEventReceiverType.ItemUpdated);

                    currentCT.Update(true, false);

                }
                catch (Exception ex)
                {
                    throw new SPException("Failed to add event receiver to content type", ex);
                }

            }

        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {

            foreach (string ct in cts)
            {
                try
                {
                    SPContentTypeId ctId = new SPContentTypeId(ct);
                    SPSite site = properties.Feature.Parent as SPSite;

                    if (site == null)
                    {
                        throw new ArgumentException("Feature must be activated at an SPSite level");
                    }

                    SPContentType currentCT = site.RootWeb.ContentTypes[ctId];

                    if (currentCT == null)
                    {
                        throw new ArgumentException(string.Format("Content Type ID {0} not found"));
                    }
      string evClass = typeof(AWBEventHandler).FullName;
                    UnregisterAllEventHandlers(currentCT, evClass);

                }
                catch (Exception ex)
                {
                    throw new SPException("Failed to remove event receiver from content type", ex);
                }
            }
        }
        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
        }

        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
        }

        private void RegisterEventHandler(SPContentType ct, string name, int sequence,
            string evAssembly, string evClass, SPEventReceiverType type)
        {
            SPEventReceiverDefinition oEv = ct.EventReceivers.Add();
            oEv.Name = name;
            oEv.Type = type;
            oEv.Assembly = evAssembly;
            oEv.Class = evClass;
            oEv.SequenceNumber = sequence;
            oEv.Data = string.Empty;
            oEv.Update();
        }

        private void UnregisterAllEventHandlers(SPContentType ct, string evClass)
        {
            bool bContinueDelete = true;
            while (bContinueDelete)
            {
                if (ct.EventReceivers.Count < 1)
                {
                    bContinueDelete = false;
                }
                else
                {
                    bool bFoundOne = false;
                    foreach (SPEventReceiverDefinition d in ct.EventReceivers)
                    {
                        if (d.Class == evClass)
                        {
                            d.Delete();
                            ct.Update(true,false);
                            bFoundOne = true;
                            break;
                        }
                    }
                    if (!bFoundOne)
                    {
                        bContinueDelete = false;
                    }
                }

            }
        }
    }
}

This code will register/unregister my AWBEventHandler class, for Content Types based on Documents (oxo1o1). By adding more elements into the array at the top, we could register against more content types. Naturally, a more sensible thing would be to have a Feature Property elements feeding in things like the Assembly, Class, Event Name, and Content Types, but this is just an example.

Note that the UnregisterAllEventHandlers is a little strange. Normally, I’d write something like:

private void UnregisterAllEventHandlers(SPContentType ct, string evClass)
{
    List<SPEventReceiverDefinition> toRemove = new List<SPEventReceiverDefinition>();
    foreach (SPEventReceiverDefinition d in ct.EventReceivers)
    {
        if (d.Class == evClass)
        {
            toRemove.Add(d);
        }
    }
    foreach (SPEventReceiverDefinition d2 in toRemove)
    {
        d2.Delete();
    }
}

In that function, we iterate over the SPEventReceiverDefinitionCollection generating a list of things to delete, and then delete them. We don’t delete them in the first for loop as deleting an item invalidate the enumerator for the for loop – so it throws an exception. Anyway, I tried using that function, and it didn’t remove all of the event receivers. I don’t know why – it works fine on lists. Instead, I developed the weird looking function shown above.

All of which is fine, but this still leaves a question – will these events be registered for child content types? In our code, we’ve got the line:

currentCT.Update(true, false);

That .Update(true,false) call will update child content types, and not throw an exception if it tries to update a sealed content type (important if you’re trying to register against something as general as the ‘Document’ content type. So the short answer is yes, our event receiver will be registered against child content types.

For example, I had a document library which allowed two document content types:

After registering against the Site content type for Documents, I used SharePoint Manager to see what event receivers I had registered in my Site collection. The image below shows a Library which has only one content type at the top, and one with 2 content types below. You can see that there are 4 event receivers registered per content type.

Note – if you change the Content Type after uploading a document – which you can through the user interface – and save that change then the other content type’s event receiver is run for ItemUpdated.

What might be interesting is the question – if we got our SPContentType object from an SPWeb (as above) – therefore as a Site Content Type, and didn’t apply the event receiver to child content types, would the list content types get the event receivers? Short answer – existing children would not, but I’m pretty sure that new child content types would.

So, in summary – above we’ve seen how to register and unregister an event receiver against a content type and all it’s child content types. And that seems to work pretty well! This has the advantage that it’s pretty simple, therefore, to add an event receiver across an entire Site Collection in one feature, rather than need a feature activated for each Web.

Advertisements
Registering EventHandlers against ContentTypes

7 thoughts on “Registering EventHandlers against ContentTypes

  1. frevd says:

    good stuff.
    but you know what? actually you can register eventreceivers with contenttype-definitions in caml:

    ItemAdded
    ItemAdded
    1
    Blas.BlaItemEventReceiver, Bla, Version=1.0.0.0, Culture=neutral, PublicKeyToken=…

  2. frevd says:

    oh, tags got stripped, again:

    [Elements xmlns=”http://schemas.microsoft.com/sharepoint/”]
    [ContentType ID=”0x010101002D36F0E792F94082B5F0B2853F595DDE” Name=”Bla” Group=”Blas” Description=”Blabla” Version=”1″]
    [XmlDocuments]
    [XmlDocument NamespaceURI=”http://schemas.microsoft.com/sharepoint/events”]
    [Receivers]
    [Receiver]
    [Name]ItemAdded[/Name]
    [Type]ItemAdded[/Type]
    [SequenceNumber]1[/SequenceNumber]
    [Class]Blas.BlaItemEventReceiver, Bla, Version=1.0.0.0, Culture=neutral, PublicKeyToken=…[/Class]
    [/Receiver]
    [/Receivers]
    [/XmlDocument]
    [/XmlDocuments]
    [/ContentType]
    [/Elements]

    1. Yup, indeed you can – but as a part of the Content Type. I’m not aware of a way in CAML of attaching to and existing content type

  3. vijay says:

    hi,

    So, in the above scenario we need to have a feature xml with scope ‘Site’ and the corresponding receiver assembly, how should the elements xml within feature should refer.

    Thanks

  4. Sen Jacob says:

    “Anyway, I tried using that function, and it didn’t remove all of the event receivers. I don’t know why”

    Can you check whether this works or not? ‘coz I’m pretty new to SP šŸ˜›

    private void UnregisterAllEventHandlers(SPContentType objSPContentType, string evtClass)
    {
    List eventReceiverDefinitionsToRemove = new List();
    foreach (SPEventReceiverDefinition objSPEventReceiverDefinition in objSPContentType.EventReceivers)
    {
    if (objSPEventReceiverDefinition.Class == evtClass)
    {
    eventReceiverDefinitionsToRemove.Add(objSPEventReceiverDefinition);
    }
    }
    while (eventReceiverDefinitionsToRemove.Count > 0)
    {
    eventReceiverDefinitionsToRemove[0].Delete();
    objSPContentType.Update(true, false);
    }
    }

    1. Without sitting down and thinking about it too much, yes, though I’d hold off updating the content type until after all event receivers have been removed. The Update could be quite expensive.

  5. Jyothsna says:

    I added content type.update(true,true) where contenttype is the sitecontent type.
    but when i upgrade the event receiver by adding one more event and register for the content type. for the doclib for which content type is alreday added the events ate not working. but when i delete the content type from doc lib and add it again newly then the events are working. Can u plz help me in this?? so is it that for the doc lib for which content type is added before upgrade of event receiver it ont work???

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s