UPDATE
For those who missed my original article (SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml) , you can find it here!
I finally got round to posting the code that goes with this article ..
Please find below the details on code for an SPFeatureReceiver class, that will automatically push down changes from your Content Type Features
(you will need to attach the code to your feature using the ReceiverAssembly and ReceiverClass attributes in your feature.
The code needs to be created as a Class in a Class Library, with Microsoft.SharePoint.dll added as a reference, and then added as a Using statement in the class itself.
Code Sample
EDIT - Thanks to a quick spot from Nick's SharePoint blog, I have updated the code to match! Thanks Nick!
ContentTypeInstaller : SPFeatureReceiverclass
{
// CODE DESCRIPTION // ---------------- /*
* This is a feature handler which should be paired
* with a content type feature.
*
* We have identified a problem with Content Types,
* where the content type site columns are not pushed
* down to custom list definitions, until they are
* "modified" first.
*
* This feature will interrogate all of the associated
* xml files that are attached to the feature.
* Once found, it will "modify" each of the custom fields
* that are referenced.
*
* This should allow list definitions to use the content
* type columns, without declaring them in the schema.xml
*/
public override void FeatureActivated(SPFeatureReceiverProperties properties) {
using(SPSite site = (SPSite)properties.Feature.Parent) {
SPWeb web = site.OpenWeb(
""); // loop through each of the elements in the feature foreach (SPElementDefinition element in properties.Definition.GetElementDefinitions(CultureInfo.CurrentCulture)) {
#region
Loop through feature elements XmlNode content = element.XmlDefinition;
// only continue if the element is a content type definition if (content.Name == "ContentType") {
// grab a new Content Type object string strCTypeID = content.Attributes["ID"].Value; SPContentType cType = web.ContentTypes[
new SPContentTypeId(strCTypeID)]; #region
Get FieldRef Order from Content type // grab the original order, we will need this later string[] fieldOrder = new string[cType.FieldLinks.Count]; int x = 0; foreach (SPFieldLink fieldlink in cType.FieldLinks) {
fieldOrder[x] = fieldlink.Name;
x++;
}
#endregion
#region
Add new columns to the content type // loop through each sub-node in the Content Type file foreach (XmlNode node in content.ChildNodes) {
#region
loop through sub nodes // only continue for // the FieldRefs collection if (node.Name == "FieldRefs") {
foreach (XmlNode fieldRef in node.ChildNodes) {
#region
Loop through FieldRefs // only apply for FieldRef tags if (fieldRef.Name == "FieldRef") {
// get the FieldID and use it to
// retrieve the SPField object string fieldID = fieldRef.Attributes["ID"].Value; //SPFieldLink fieldLink = cType.FieldLinks[new Guid(fieldID)]; SPField field = cType.Fields[
new Guid(fieldID)]; // first we need to remove the fieldref cType.FieldLinks.Delete(
new Guid(fieldID)); // and save, pushing this change down cType.Update(
true); // NOTE - this will NOT delete any content // in existing lists! // now add the field back in again cType.FieldLinks.Add(
new SPFieldLink(field)); // and call an update, pushing down all changes cType.Update(
true); // NOTE - this is what adds the column to those // lists who don't already have it. }
#endregion
}
}
#endregion
}
#endregion
#region
Apply changes // reset the field order // it is possible that adding and // removing fields would have // affected this cType.FieldLinks.Reorder(fieldOrder);
// force update of the content type, // pushing down to children cType.Update(
true); #endregion
}
#endregion
}
}
}
public override void FeatureDeactivating(SPFeatureReceiverProperties properties) {
}
public override void FeatureInstalled(SPFeatureReceiverProperties properties) {
}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {
}
}
Enjoy, and happy coding!
Great post Martin. You should include the content from the MSDN forum post as well though!
ReplyDeleteThanks. I added a link to the original post to the article.
ReplyDeleteCool feature. But what would be even more cool, is a receiver that would push down all changes as well.
ReplyDeleteLet's say you have a content type that is activated on a list and all is fine. Then some time later you decide to change that content type feature xml, say add a few more columns, rename a few, delete one - that sort of changes. As things are OOB with MOSS/WSS those changes are not pushed through on lists the the content type already is attached. This is "by design":
http://msdn.microsoft.com/en-us/library/aa543504.aspx (Look in the Important-section)
The issue is known in the dev community:
http://social.msdn.microsoft.com/Forums/en-US/sharepointecm/thread/75548e96-6bae-4c32-bf68-2965570a3579/
But so far I haven't seen an open source feature that would fix this little thing once and for all. It would be a really neat feature to have.
Cheers!
thanks
ReplyDeleteHi Martin, do I need to do anything special if my CTs have been set to read-only via the UI?
ReplyDeleteNo, the read-only attribute applies to the user interface. Code pretty much ignores it.
ReplyDeleteHi martin
ReplyDeleteI have one question. In my sharepoint site already one content type is there. That content type consists one column(dropdown). My requiremnt is, i have to fill that column with items which will come from external data base(e.g. Oracle). So how can i achive this. Please help. Thanks in advance.
Will this code activate with a solution upgrade, or are you required to deactivate and reactivate the feature for every site collection? I'd love any ideas around automating this at the web app or farm level.
ReplyDeletePaul, you could write a Timer job to reactivate the feature?
ReplyDeleteOtherwise you can use PowerShell or scripts to call the STSADM commands to activate the features?
Finally, you could do this with an Object Model command line app?
I have a single problem with your solution - if I am adding a FieldRef to a Field that is deployed in the same feature.xml, first attempt at activating will fail - the second one will succeed. Is it possible to force the creation of Fields (site columns) before FeatureActivated attempts to use them?
ReplyDeleteI have to admit, I always as a matter of practice have my fields and content types in separate features (thats also how Microsoft do it, with the oob "fields" and "ctypes" features).
ReplyDeleteI'm sure the code could be modified to change the order in which it executes.
I don't put any royalty restrictions on what I post so feel free to copy the code into your environment and have a play :)
This is outstanding, though I have a question about it. If you want to override the default display behavior for a field don't you have to explicitly define it on the list in the Fields node?
ReplyDeleteI've been trying to accomplish this through the column definitions and the content type definitions with no luck. Unless I create a Field element in the list itself with ShowInNewForm="FALSE" I can't hide a column.
Any thoughts?
anon,
ReplyDeleteYou can set these values on the SPField (as the site column).
When you modify the site column (and push down to the lists) then it should apply those settings too..
However, you can of course override this setting at the list level!