First, a short intro - I'm starting this blog for 2 reasons - 1, I'm learning a lot of stuff right now that if not used regularly, I'll forget - and need a quick way to document some of it, and 2, if it weren't for the other people out there doing the same, I wouldn't have learned a lot of what I have. So hopefully my experiences will help someone else out there, too.
Now, on to the good stuff. The problem I've been tackling this week is in MOSS 2007 - specifically, the deployment and propagation of Content Types in an enterprise environment, using the Features & Solutions Framework. Here's the background:
- MOSS is being used as a publishing platform for a very large project. (Hundreds of thousands of webs).
- Following best practices for enterprise MOSS architecture, Content Types are being deployed through Features, and are inherited by pages (list items) in subwebs.
The challenge is that MOSS really doesn't support updates to content types through the Features & Solutions framework (i.e., you are using XML Based Content Types). Once you've deployed your feature, it's going to be a little bit of a fight to update a content type by upgrading the solution. On the other hand, MOSS's editing capabilities of content types through the UI works extremely well. So, my goal has been to come up with a way to replicate the "UI" behavior within the confines of the Features & Solutions framework.
The first thing I discovered is that if you Deactivate your feature, Retract it, Delete it, Install it, Deploy it, and Activate your feature again, new fields that you've added to the Content Type in XML do, in fact, appear. The only problem is, everything that inherits from that Content Type (let's say, oh, a few million list items?) does not see any of the changes. This is because a new "list" Content Type is created any time you assign a content type to a list, and the new List Content Type inherits from the "real" one that you deployed through your feature. Soren Nielsen describes this briefly in his blog post.
So, at this point, the problem that still remains is that we need a way to propagate changes from a Parent Content Type down to its Child Content Types (in this case, pages in a publishing web that inherit from it). Microsoft's MSDN article on Updating Child Content Types pretty much spells this out for us:
"You cannot add columns to an existing site content type declaratively, in other words, by updating the Feature XML files."
Unfortunately, we simply can't create a business requirement in an enterprise MOSS installation that says "once we deploy content types, they can never change."
There are a few blogs out there that have proposed the same solution, as well as a Microsoft engineer that worked with me on this issue - use the MOSS Object Model to recurse individually through each subweb, check each list, compare fields, and programmatically add the field links that don't exist. Some have even integrated this into a STSADM command. I gave this a shot, and it takes between 0.2 - 0.4 seconds to check and compare fields on a list item. This may not sound like a lot (and will work fine for a smaller MOSS install), but for this particular environment, this technique would take at least 66 hours (2.7 days) to propagate Content Type fields.
You may notice, though, that MOSS 2007 out of the box has a special little radio button in the UI where you add a new column to a content type - "Update all content types that inherit from this content type" (yes/no). Propagating new columns/fields this way works dramatically faster - about 0.04 seconds per WEB. This is much more acceptable.
Kicking this around with a team member, we eventually understood what MOSS was actually doing. MOSS, out of the box, during a Content Type update, creates a list of changes to a content type, and then will propagate those changes via the SPContentType.Update() method. The cool thing, is that .Update() takes an optional boolean parameter - "UpdateChildren". This will propagate the current set of changes to child content types (which in our case are lists). Tested it, and it works! Essentially, there's no way to make the Features & Solutions framework call .Update(true) - if we can find a way to do this, we have our solution.
At the end of the day, we need to update the Content Type XML files, but programmatically add or make changes to the fields ourselves in the Object Model. So I've modified the steps to deploy a solution with a small utility to go along with it. Here is the procedure:
- Upgrade the revised solution in-place (no deactivating features). At this point you will NOT see any new fields/columns in the content type.
- Compare the fields in your revised solution (WSP) file with the fields and field links that currently exist in your "Live" Content Type as it currently exists in the MOSS farm. I wrote a utility to accomplish this by extracting and parsing the contents of the WSP package and make a class model of the fields.
- For any fields that don't exist, create the field (SPField) and add a field link (SPFieldLink) to the Content Type.
- Call SPContentType.Update(true).
As long as you can make field/column changes programmatically and .Update(true) them in one shot, MOSS does the rest (and much faster than enumerating through every list).