Thursday, October 15, 2009

Update content types and site columns with feature receiver using the standard manifest file

The usual way of creating content types and site columns is by using an element manifest file with the standard Field and ContentType elements in a feature. The creation is quite easy but when it comes to updating you see some really hard times – first Microsoft strongly discourages the modifying of the original manifest files and second even if you do that the changes you made won’t get propagated to the target lists. Quite unpleasant, isn’t it. So, to address this problem I created a feature receiver that can read a manifest file and apply all changes to the existing artifacts propagating the changes to the site’s lists (actually this receiver is an enhanced version of my “common actions” receiver from my previous posting).

First – one big note – the updating receiver is still a beta quality code and should be used carefully – there is a command line utility in the receiver’s solution that allows starting the update routine directly and also supports a “test run” mode – so you may consider using this first and check the verbose notifications that it outputs as to which changes it is about to apply (more on this later).

Another important note – do not modify the element manifest file of the original feature that created the site columns and content types – use a separate file, a copy of the original one with the desired modifications. This is because before the artifact is updated with the object model its schema is read from the manifest file in the file system (you can check that easily – the schema is saved to the content database after the artifact gets its update method called). This also means that you can modify this way “ghosted” site columns and content types (MSDN recommends strongly against that) but not such that were already created or modified with the object model. The solution described here handles both cases.

And several words on what it actually updates and how exactly it applies the updates – 1st starting with the latter – only standard object model calls are used – the Update methods of the SPField and SPContentType classes are used with the option of pushing down the changes to the corresponding list fields and inheriting content types respectively. So the idea is not just to update the site columns and content types but to propagate these changes to the lists where these are actually used. The SPField objects are updated with setting the field schema from the manifest file to the SchemaXml property of the field – one note here – all attributes missing in the manifest schema that are present in the existing field schema are copied from the latter – so this is actually a merge of the two field definitions rather than a total overwriting from the schema in the manifest file (I wanted to keep safe here for cases like lookup fields which have attributes that are normally set after the field is created). So, in cases when the existing site columns are modified from the UI or from code after the site is created the updating with the receiver may result in a merge of the field definition rather than a definition that is identical to the one in a newly created site. Note – only existing site columns are being modified, if a column from the manifest file is not found in the site, it is skipped.

And about content types – two elements are updated here – the FieldRefs and the XmlDocuments. For FieldRefs – you can add, remove, change the attributes of the FieldRef-s of the content type as well as reorder them. All attributes for which public settable properties in the SPFieldLink class exist are set (exceptions being – ShowInNewForm and ShowInEditForm – no properties for them). XmlDocuments are also added, updated and deleted according to the changes in the content type schema. Something interesting (and maybe peculiar) here – the XmlDocuments are also inherited from the base content types – unless a XmlDocument with the same namespace exists in the derived content type which overrides the parent’s definition. This leads to some unexpected results especially with the list item event receiver XmlDocument – because of this inheritance mechanism you may end up with one and the same event receiver’s method being called more than once on a list item modification event (but that’s another story). Another “intrinsic” behavior with the event receiver XmlDocument – when you add receivers with it they are added to the lists to which the content type is bound, but if you remove the XmlDocument from the schema definition – these remain in the destination lists, though if you modify the receivers XmlDocument – the old receivers are removed and the new ones are added (basically this is what we have with the object model). I also tested modifications of the FormTemplates and FormUrls XmlDocument elements and they seem to work just fine. Note about content type updates – only existing site content types are being modified, if a content type from the manifest file is not found in the site, it is skipped.

Here is a feature.xml from a sample feature that uses the receiver:

<?xml version="1.0" encoding="utf-8"?>

<Feature

    Title="ZCommonTest"

    Description="ZCommonTest"

    Id="7A0F010D-7993-4bcd-9127-7B15FEFFC0FF"

    Scope="Web"

    Hidden="TRUE"

    DefaultResourceFile="core"   

    ReceiverAssembly="Stefan.Sharepoint.Util, Version=1.0.0.0, Culture=neutral, PublicKeyToken=338fd4db0d2cb397"

    ReceiverClass="Stefan.Sharepoint.Util.FeatureReceivers.CommonActionsReceivers"

    xmlns="http://schemas.microsoft.com/sharepoint/">

    <ElementManifests>

    </ElementManifests>

  <Properties>

    <Property Key="CTsFieldsUpdatePath" Value="fields.xml,ctypes.xml" />

  </Properties>

</Feature>

So basically, you need to set the appropriate ReceiverAssembly and ReceiverClass attributes and the custom Property element with Key=”CTsFieldsUpdatePath” – the value of the property element can contain one or many (comma separated) manifest files’ paths – the feature folder relative path actually.

In the receiver’s solution there is also a console utility project – the tool has a command for direct invocation of the update functionality, here is a sample call:

UtilTest.exe -o updatecontenttypesandfields -weburl http://racoon-vpc-hg:2909/sites/4 -manifestpath "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\ZCommonTest\ctypes.xml" -verbose -log z.log

Most of the arguments don’t need any clarification as to their usage. It is a good idea to use the –verbose and/or -log parameters – they will output useful information and details about the changes that are applied. There is yet another parameter that you may consider using: –testrun – when applied you see just the output with the changes that would have been applied in normal mode without actually calling the update methods for the fields and content types (you won’t see the correct change notifications for some content types if you have modifications in both base and derived content types though for obvious reasons).

You may download the solution from here (it is the same one as in my previous posting – you can check the installation notes in it too).

7 comments:

  1. Hi Stefan, Your code works like a charm.. Thank you so much for that..But Do you have any idea, how to propogate ShowInNewForm, ShowInEditForm changes in the XML file to List level content types?

    ReplyDelete
  2. Hi AnuPriya, you can get these two changed but ... with reflection only. Unfortunately Microsoft didn't create public properties in the SPFieldLink class for these two attributes of the FieldRef schema, so I didn't want to mess with that when I wrote the updating feature receiver. For the ShowInEditForm attribute you have two private member variables of the SPFieldLink class that should be set if you need this change to get propagated: m_bEditFormExplicit and m_bShowInEditForm (for the ShowInNewForm - m_bNewFormExplicit and m_bShowInNewForm). The former should be set to true and the latter as the value of the corresponding FieldRef attribute. Hope this helps, though you'll need to make changes to the code.
    Greetings
    Stefan

    ReplyDelete
  3. Thanks stefan for your input. But can you tell me, what is the general practice to update exising content types deployed through feature? Can we add/modify/update fields and all its attributes of the existing content types through object model
    and propogate the changes to the inherited ones through code. The web interface allows us to do only some basic changes to the columns in the content types like making it as hidden, required and optional.
    How one can do changes to other attributes of FieldRef ? Reflection is the only option?? Please suggest me if you know any better way of handling this.

    ReplyDelete
  4. Hi AnuPriya, as regards the general (or at least official) guidelines for updating content types we have this from microsoft: http://msdn.microsoft.com/en-us/library/aa543504.aspx
    Basically you can create content types and update almost every element in a content type using the object model - the two attributes that you noticed are maybe the only exceptions. So you need to use reflection only if you need to update the ShowInNewForm and ShowInEditForm attributes of the FieldRef element. And ... you may have complications (unfortunately) if you need to push changes from a site content type to the inheriting list content types - you may check my lengthy posting on that - http://stefan-stanev-sharepoint-blog.blogspot.com/2009/11/content-type-features-revisited.html
    You may update list content types directly as a work-around to the issues with pushing down changes from site content type but this requires extra work. The conclusion here is - well, content types in SharePoint are quite a tricky thing.

    ReplyDelete
  5. Thanks Stefan...good article

    ReplyDelete
  6. Great stuff again. Thanks for making time to write these articles. You will be my number one resource, Looking forward to more articles
    SharePoint Training Online

    ReplyDelete