SharePoint Content Type Lifecycle Management
That’s just a fancy term for updating content types via feature upgrades.
In SharePoint 2010, there’s a new UpgradeActions element and a handy new AddContentTypeField element as well.
There are a couple of good blog posts that discuss this topic and an unusually useful MSDN article on the topic of application lifecycle management in SharePoint 2010. These more than cover the topic of adding new fields to existing content types via upgrades.
One key point from this article is the following section of text:
This section describes at a high level how you can put these feature-versioning and upgrading capabilities to work. When you create a new version of a feature that is already deployed on a large SharePoint 2010 farm, you must consider two different scenarios: what happens when the feature is activated on a new site and what happens on sites where the feature already exists. When you add new content to the feature, you must first update all of the existing definitions and include instructions for upgrading the feature where it is already deployed.
For example, perhaps you have developed a content type to which you must add a custom site column named City. You do this in the following way:
- Add a new element file to the feature. This element file defines the new site column and modifies the Feature.xml file to include the element file.
- Update the existing definition of the content type in the existing feature element file. This update will apply to all sites where the feature is newly deployed and activated.
- Define the required upgrade actions for the existing sites. In this case, you must ensure that the newly added element file for the additional site column is deployed and that the new site column is associated with the existing content types. To achieve these two objectives, you add the ApplyFeatureManifests and the AddContentTypeField upgrade actions to your Feature.xml file.
It would seem that this would be a great mechanism for managing your metadata upgrades. As bullet number 2 implies, one simply adds the new field to your existing element manifest file for new installs and add a new element manifest file plus a AddContentTypeField element to your feature XML for upgrades.
After trying this out for almost two days now, I can say with some certainty that this does not work as documented or designed. You cannot add the field to both your original element manifest XML (for new installs) and to a separate element manifest file for upgrades. It does not work. After performing an upgrade, the symptom is that the field does not get pushed down to the list level content types that inherit from the site level content type. If you go to add the field manually, you will see the field appear twice:
You can see the result of two passes of testing here. At the site level, the content type is fine and dandy; it’s only at the list level where things go wrong. Obviously, this is less than ideal. I tried numerous variations of feature setup to see if I could get it to work without code and none of them worked.
Ultimately, the solution that worked for me was to add a CustomUpgradeAction as well in addition to the AddContentTypeField.
The upgrade action has the following code in it:
1 2 3 4 5 6 7 8 9 |
// Delete the field link from the content type at the site level. contentType.FieldLinks.Delete(fieldId); contentType.Update(true); // Add it again and force it to push down. contentType.FieldLinks.Add(fieldLink); contentType.Update(true); |
It’s a lot of redundancy, but ultimately, the only way I could get the upgrade to work with the field in both the original element manifest (desired for new installs) and in a new element manifest for upgrades. I should note that if you do NOT add the field to the original element manifest, everything works as documented. But this is a less desirable scenario as it means that every clean install would also require all of the updates to be run afterwards.
Here is the full listing
My feature.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?xml version="1.0" encoding="utf-8"?> <Feature Id="0C4C1210-B056-4AFC-B556-A8BB30E1A9F1" Title="Zaang Labs GameTime Content Types" Description="Installs the content types used by Zaang Labs GameTime." Hidden="FALSE" Scope="Site" DefaultResourceFile="core" ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17" ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.MetadataPrimer" Version="1.0.0.1" xmlns="http://schemas.microsoft.com/sharepoint/"> <ElementManifests> <ElementManifest Location="zl_gametime_contenttypes.xml" /> </ElementManifests> <Properties> <Property Key="zaanglabs.prime.if.contains" Value="ZaangLabs,Zaang Labs"/> <Property Key="zaanglabs.mark.for.removal" Value="GameTime Configuration,GameTime Huddles"/> <Property Key="zaanglabs.config.assembly" Value="ZaangLabs.GameTime.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17"/> <Property Key="zaanglabs.config.resource.path" Value="ZaangLabs.GameTime.Core.zaanglabs.custom.web.config.xml"/> </Properties> <UpgradeActions ReceiverAssembly="ZaangLabs.Server.Framework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4df39f66bb4e1d17" ReceiverClass="ZaangLabs.Server.Framework.Components.FeatureReceivers.ContentTypeUpdater"> <VersionRange BeginVersion="1.0.0.0" EndVersion="1.0.0.1"> <ApplyElementManifests> <ElementManifest Location="zl_upgrade_1_0_0_1.xml"/> </ApplyElementManifests> <AddContentTypeField ContentTypeId="0x01002167C000000000000000000000000002" FieldId="{0bc82141-5fa4-4948-b7b9-888f10010020}" PushDown="TRUE" /> <CustomUpgradeAction Name="AddFields"> <Parameters> <Parameter Name="add.field.1">0x01002167C000000000000000000000000002,{0bc82141-5fa4-4948-b7b9-888f10010020}</Parameter> </Parameters> </CustomUpgradeAction> </VersionRange> </UpgradeActions> </Feature> |
A snippet of the original element manifest with the new field added to the content type for new deployments:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!-- Other fields omitted --> <Field DisplayName="Share Milestones With" Name="Share_Milestones_With" StaticName="Share_Milestones_With" ID="{0bc82141-5fa4-4948-b7b9-888f10010020}" Description="Leave this blank to use a new set of milestones for this Huddle. Otherwise, select an existing Huddle to share Milestones with." Type="Lookup" ShowField="Title" List="Self" SourceID="schema.zaanglabs" Group="Zaang Labs" /> <ContentType Name="Huddle" ID="0x01002167C000000000000000000000000002" Description="Zaang Labs Huddle." Group="Zaang Labs"> <FieldRefs> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000010}" Name="Huddle_Description" Required="TRUE" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000011}" Name="Huddle_Collaborators" Required="TRUE" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000012}" Name="Huddle_Documents" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000013}" Name="Huddle_Milestones" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000014}" Name="Huddle_Tasks" Required="FALSE" ShowInNewForm="FALSE" ShowInEditForm="FALSE" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000015}" Name="Remove_Artifacts" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000016}" Name="Huddle_Documents_List_ID" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000017}" Name="Huddle_Tasks_List_ID" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000018}" Name="Huddle_Milestones_List_ID" /> <FieldRef ID="{0BC82141-5FA4-4948-B7B9-888F00000019}" Name="Huddle_Owner" ShowInNewForm="FALSE" ShowInEditForm="FALSE"/> <FieldRef ID="{0bc82141-5fa4-4948-b7b9-888f10010020}" Name="Share_Milestones_With"/> </FieldRefs> <!-- Omitted --> </ContentType> |
The new upgrade element manifest:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="utf-8" ?> <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> <Field DisplayName="Share Milestones With" Name="Share_Milestones_With" StaticName="Share_Milestones_With" ID="{0bc82141-5fa4-4948-b7b9-888f10010020}" Description="Leave this blank to use a new set of milestones for this Huddle. Otherwise, select an existing Huddle to share Milestones with." Type="Lookup" ShowField="Title" List="Self" SourceID="schema.zaanglabs" Group="Zaang Labs" /> </Elements> |
And the custom feature receiver (with portions omitted):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
using System; using System.Collections.Generic; using Microsoft.SharePoint; using ZaangLabs.Server.Framework.Components.Deployment; namespace ZaangLabs.Server.Framework.Components.FeatureReceivers { /// <summary> /// Updates /// </summary> public class ContentTypeUpdater : FeatureReceiverBase { /// <summary> /// Triggered when a feature is updated. /// </summary> /// <param name = "properties">The properties.</param> /// <param name = "upgradeActionName">Name of the upgrade action.</param> /// <param name = "parameters">The parameters.</param> public override void FeatureUpgrading(SPFeatureReceiverProperties properties, string upgradeActionName, IDictionary<string, string> parameters) { if(upgradeActionName.ToUpperInvariant() != "ADDFIELDS") { base.FeatureUpgrading(properties, upgradeActionName, parameters); return; } try { SPSite site = GetSite(properties); using (SPWeb web = site.RootWeb) { web.AllowUnsafeUpdates = true; foreach (string key in parameters.Keys) { // Iterate and split each content type, field value. string value = parameters[key]; string[] parts = value.Split(','); SPContentTypeId contentTypeId = new SPContentTypeId(parts[0]); Guid fieldId = new Guid(parts[1]); SPField field = web.Fields[fieldId]; SPFieldLink fieldLink = new SPFieldLink(field); SPContentType contentType = web.ContentTypes[contentTypeId]; Log("Adding field \"{0}\" to content type \"{1}\".", field.Title, contentType.Name); contentType.FieldLinks.Delete(fieldId); contentType.Update(true); contentType.FieldLinks.Add(fieldLink); contentType.Update(true); } web.AllowUnsafeUpdates = false; } } catch(Exception exception) { Log("Failed to execute the custom upgrade action: {0}", exception); } base.FeatureUpgrading(properties, upgradeActionName, parameters); } } } |