[Code Update] SDK and Microsoft Press Both Wrong?? Custom Fields in the schema.xml

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!
 

class

ContentTypeInstaller : SPFeatureReceiver

{

// 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

 

// retrieve the xml content for the feature element

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!

  • Martin Hatch

    anon,

    You 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!

  • Anonymous

    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?

    I'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?

  • Martin Hatch

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

    I'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 🙂

  • skolima

    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?

  • Martin Hatch

    Paul, you could write a Timer job to reactivate the feature?
    Otherwise 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?

  • Paul

    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.

  • Sai

    Hi martin
    I 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.

  • Martin Hatch

    No, the read-only attribute applies to the user interface. Code pretty much ignores it.

  • Paul

    Hi Martin, do I need to do anything special if my CTs have been set to read-only via the UI?

  • T8tube.com

    thanks

  • Peter

    Cool feature. But what would be even more cool, is a receiver that would push down all changes as well.

    Let'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!

  • Martin Hatch

    Thanks. I added a link to the original post to the article.

  • Daniel Larson

    Great post Martin. You should include the content from the MSDN forum post as well though!