Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating already-deployed SharePoint content types to handle additional item events

I have a site content type that was used for a handful of lists throughout my site collection. In that content type, I describe an event receiver to handle the ItemAdding event. This works fine. Now I need to update the content type so that ItemUpdating is also handled. Off the top of my head, I tried simply modifying the xml for my content type, since this seemed to allow for easy version tracking. This worked in the sense that my updates were applied to the site content type, but not to my lists that had been using this content type. This much was expected. Then I noticed that the SharePoint SDK takes a grim view of that:

Under no circumstances should you update the content type definition file for a content type after you have installed and activated that content type. Windows SharePoint Services does not track changes made to the content type definition file. Therefore, you have no method for pushing down changes made to site content types to the child content types.

The SDK then points to a couple sections which describe how to use the UI or code to push changes. Since the UI offers no hook into event receivers, I guess I will be choosing the code path.

I thought I'd be able to do something like this and just add a new event receiver to the list's copy of the content type:

SPList list = web.Lists["My list"];
SPContentType ctype = list.ContentTypes["My content type"];
// Doesn't work -- EventReceivers is null below.
ctype.EventReceivers.Add(SPEventReceiverType.ItemUpdating, 
                         "My assembly name", "My class name");

But the catch is that ctype.EventReceivers is null here, even though I have ItemAdding already hooked up to this list. It appears that it was moved to the list itself. So, the list has a valid EventReceivers collection.

SPList list = web.Lists["My list"];
list.EventReceivers.Add(SPEventReceiverType.ItemUpdating, 
                        "My assembly name", "My class name");

So, I have a couple questions:

  1. Is the correct way to do this just to add any new event receivers directly to the list and just forget about my content type altogether?
  2. To accomplish this change, what's the best way to handle this in terms of configuration management? Should I create a simple console app to find all the appropriate lists and modify each of them? Or is somehow creating a Feature a better option? Either way, it seems like this change is going to be off on its own and difficult to discover by future devs who might need to work with this content type.
like image 537
Chris Farmer Avatar asked Jul 02 '09 20:07

Chris Farmer


2 Answers

Did you call ctype.Update(true) after adding the EventReceiver? If you don't it won't be persisted . And don't use the List content type, use SPWeb.ContentTypes instead.

This code works for me:

var docCt = web.ContentTypes[new SPContentTypeId("0x0101003A3AF5E5C6B4479191B58E78A333B28D")];
//while(docCt.EventReceivers.Count > 0)
//  docCt.EventReceivers[docCt.EventReceivers.Count - 1].Delete();
docCt.EventReceivers.Add(SPEventReceiverType.ItemUpdated, "ASSEMBLYNAME, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c5b857a999fb347e", "CLASSNAME");

docCt.Update(true);

The true parameter means it gets pushed down to all child ContentTypes as well. (i.e. to all lists using the content type).

like image 195
Colin Avatar answered Oct 16 '22 09:10

Colin


To answer the second part of your question, this a tricky thing because of the fact that changes to contenttypes on the sitecollection won´t be pushed down to the lists where it´s used. A "copy" is essentially made of the fields in the sitecollection and there is no more link between them after you add a contenttype to the list. I think this is due to the fact that you are supposed to make changes to lists without it affecting the sitecollection. Anyhow my contribution to this "problem", and how I have solved it, involves making the xml the "master" and in a featurereceiver I pull up the xml and find all places where the contenttype is used and from there update the contenttypes (really the fieldrefs) on list level to match the one in the xml. The code goes something like:

var elementdefinitions = properties.Feature.Definition.GetElementDefinitions();

foreach (SPElementDefinition elementDefinition in elementdefinitions)
{
   if (elementDefinition.ElementType == "ContentType")
   {
     XmlNode ElementXML = elementDefinition.XmlDefinition;

     // get all fieldrefs nodes in xml
     XmlNodeList FieldRefs = ElementXML.ChildNodes[0].ChildNodes;

     // get reference to contenttype
     string ContentTypeID = ElementXML.Attributes["ID"].Value.ToString();
     SPContentType ContentType = 
         site.ContentTypes[new SPContentTypeId(ContentTypeID)];

     // Get all all places where the content type beeing used
     IList<SPContentTypeUsage> ContentTypeUsages = 
        SPContentTypeUsage.GetUsages(ContentType);
   }
}

The next thing is to compare the fieldrefs in xml xml with the fields on the list (done by the ID attribute) and making sure that they are equal. Unfortunately we can´t update all things on the SPFieldLink class (the fieldref) and (yes I know it´s not supported) here I have actually used reflection to update those values (f.e. ShowInEditForm ).

like image 43
Johan Leino Avatar answered Oct 16 '22 11:10

Johan Leino