views:

908

answers:

3

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. OTTOMH, 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.
+2  A: 

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).

Colin
I guess I didn't even go down that road after the SDK implied not to touch the content type definition. I now see that this is different, since you can explicitly tell it to push the changes down. This seems like the perfect way to do it. I'll try it out. Thanks! Now, how do you do this in terms of configuration management? :)
Chris Farmer
It's true that you shouldn't touch the CType def. but that means ONLY the CAML that was used to create it/ Through the object model you can do what you like. We execute above code through a feature receiver that is coupled to the feature that holds the cType defs. the while... delete was added so we can deactivate / activate the feature as many times as we want and always keep 1 eventreceiver per cType (IMHO the cleanest way to go).So your question is answered?
Colin
A: 

As far as the second part of your question, I wanted to pass along what we've done for similar situations in the past. In our situation, we needed a couple of different scrips: One that would allow us to propagate content type updates down to all of the lists in all webs and another that would reset Master Pages/Page Layouts to the site definition (un-customized form).

So, we created some custom stsadm commands for each of these actions. Doing it this way is nice because the scripts can be placed into source control and it implements the already-existing stsadm interface.

Custom SharePoint stsadm Commands

UnhipGlint
+1  A: 

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 ).

Johan Leino
Good piece of code, but not completely related to the adding of itemeventreceiver, +1 though
Colin
@Colin - Nah, it has more to do with updating changes to contenttypes. The adding of eventreceivers has already been answered I think
Johan Leino