Propagating Updates to Content Types

When you use a sitecollection contenttype on a list, what actually happens is a “copy” of the original contenttype is made and stored in the list. Thus, any changes to the original contenttype is not propagated into the list. As MS explains it:

“Do not, under any circumstances, update the content type definition file for a content type after you have installed and activated that content type. Windows SharePoint Services does not track all the changes made to the content type definition file. Therefore, you have no reliable method for pushing down all the changes made to site content types to the child content types.”

 

More on the topic can be read here:

http://soerennielsen.wordpress.com/2007/09/11/propagate-site-content-types-to-list-content-types/
http://msdn.microsoft.com/en-us/library/aa543504.aspx


A brief demonstration

My initial xml for the contenttype (and its fields), aka the feature, looks like this:

<Elements xmlns="http://schemas.microsoft.com/sharepoint">
  <ContentType
 ID="0x010008F15D6B2D0A454BA95AA5D91E21416E"
 Name="MyCustomContentType"
 Group="JL"
 Description="This is my custom content type">
    <FieldRefs>
      <FieldRef
        ID="{4e6a1cad-d0e4-4ce2-9086-8664cbb6ef73}"
        Name="CustomChoice" />
    </FieldRefs>
  </ContentType>
  <Field
 ID="{4e6a1cad-d0e4-4ce2-9086-8664cbb6ef73}"
 Name="CustomChoice"
 DisplayName="Custom Choice Column"
 Type="Choice"
 StaticName="CustomChoice"
 DisplaceOnUpgrade="TRUE"
 Group="JL">
    <CHOICES>
      <CHOICE>Choice 1</CHOICE>
      <CHOICE>Choice 2</CHOICE>
    </CHOICES>
  </Field>
</Elements>

 

 

Note: I have used this contenttype on a list that I call “My Custom List”.

I will now make a simple update to this contenttype like:

<CHOICES>
      <CHOICE>Choice 1</CHOICE>
      <CHOICE>Choice 2a</CHOICE>
      <CHOICE>Choice 3</CHOICE>
    </CHOICES>

If I look at the sitecollection contenttype the new values have been updated on the choice column:

 

Adding (or editing) an item in the list however still shows the old values:

OK, so how do we go around this issue?

The one place where I know the data is correct is the xml file containing the contenttype declaration, I´ll treat that as my “master”.

Adding a feature receiver to my contenttype feature will allow me to “pick up” that xml though the SPElementDefinition class.
The code looks like this, I´ll try to explain it at afterwards:

            SPSite sitecollection = properties.Feature.Parent as SPSite;
            using (SPWeb root = sitecollection.OpenWeb())
            {
 

                var query = from SPElementDefinition element in properties.Feature.Definition.GetElementDefinitions(CultureInfo.CurrentCulture)
                            where element.ElementType == "ContentType"
                            let xlmns = XNamespace.Get(element.XmlDefinition.NamespaceURI)
                            let ContentTypeXml = XElement.Parse(element.XmlDefinition.OuterXml)
                            let ContentTypeId = new SPContentTypeId(ContentTypeXml.Attribute("ID").Value)
                            from usage in SPContentTypeUsage.GetUsages(root.ContentTypes[ContentTypeId])
                            where usage.IsUrlToList == true
                            select new
                            {
                                UrlToList = usage.Url,
                                SiteColumns = (from fieldref in ContentTypeXml.Descendants(xlmns.GetName("FieldRef"))
                                               select root.Fields[new Guid(fieldref.Attribute("ID").Value)]).ToList()
                            };

                foreach (var result in query.ToList())
                {
                    using (SPWeb web = sitecollection.OpenWeb(result.UrlToList))
                    {
                        SPList list = web.GetList(result.UrlToList);

                        foreach (SPField fieldOnWeb in result.SiteColumns)
                        {
                            if (list.Fields.ContainsField(fieldOnWeb.InternalName))
                            {
                                SPField fieldInList = list.Fields[fieldOnWeb.Id];
                                fieldInList.SchemaXml = fieldOnWeb.SchemaXml;
                                fieldInList.Update();
                            }
                        }
                    }
                }
             }

 

So, what is happening here?

  1. To start off with I´m using LINQ to first find the SPElementDefinitions of type contenttype.
  2. Then I´m storing some values with the let parameter
    1. The xml definition of the SPElementDefinition (the actual xml in the file).
    2. The SPContentTypeId via the ID attribute of the xml stored in (a).
    3. The namespace
  3. Then I fetch the SPContentTypeUsages where they point to being used in a list
  4. I´m constructing an anonymous type containing the url to the list where the contenttype is being used together with all the SPFields from the sitecollection.
  5. Finally loop through the findings in the query and open up the lists where the contenttype is used and update each fields xml with the one from the sitecollection.

 

And now, the contenttype is updated:

I know there are some limitations (stuff you can´t accomplish through this method) but at least it´s a start…

, ,

  1. #1 by Søren Nielsen on August 12, 2009 - 17:00

    Hi Johan

    Elegant code. Nice.

    • #2 by Johan Leino on August 12, 2009 - 18:19

      Thanks 🙂 Got some inspiration from your stuff and went from there!

  2. #3 by Shrike on August 17, 2009 - 12:41

    Cool People – Cool Job!
    Hi, I’m a dumb at coding. But I remember that when I updated the XML, yes, the CT took the changes. I edited and just clicked OK to update instances and it did.

    However, once this is done, the link from XML to the CT is broken and updates no longer propagate due to editing the CT. So can’t someone extract the part of the action within the CT editing form that handles the update so we could flood down the update without going inside the edit form? In a simple wat for non programmers? Regards.

    • #4 by Johan Leino on August 22, 2009 - 16:08

      I´m sure I will have to dig deeper into this issue in the near future (actually I already have) and I´ll be sure to blog more about it then.

  3. #5 by josephjc on December 28, 2011 - 19:39

    Thanks! works on 2010 as well

  4. #6 by rajendra on April 18, 2014 - 03:09

    Hi Johan,
    When I build my code I am getting this error “Couldn’t find an implementation of the query pattern for the source type microsot.Sharepoint.administration.spelementdefinition .
    Cast not found. Are you missing a refernce to system.core.dll or using directive for system.linq”
    My project solution already has syste.core.dll. Could you please let me know am I missing any thing.

    • #7 by Johan Leino on April 18, 2014 - 06:47

      Which version of the .net fwk are you using. That error refers to linq which is in 3.5

    • #8 by Johan Leino on April 18, 2014 - 06:48

      …or maybe a “using System.Linq”….

  5. #9 by Rajendra on April 22, 2014 - 20:02

    Yeah, Thank you Johan it’s issue with the System.Linq issue.It got resolved
    One more issue occuring.My Webapplication has 16 subsites and all the sub sites refer to the single content type.
    I am trying to update field name ArticleUrlAddress from single line of text to Note type.Through Powershell script I updated single line of text to Note in the site columns
    When I am trying to update the the same field (ArticleUrlAddress) to a list through above code ,I am getting below schemaxml during debugging
    fieldInList.SchemaXml as
    fieldOnWeb.SchemaXml as
    After the fieldInList.Update(); fieldtype is not getting updated from single line of text to Note.
    Could you please help me out to resolve the issue.Am I missing anything

  6. #10 by Rajendra on April 22, 2014 - 20:04

    fieldInList.SchemaXml as
    fieldOnWeb.SchemaXml as

  7. #11 by sandy on May 28, 2014 - 06:01

    If you are using sharepoint 2010 you can use powershell script .
    You need to use the overload for SPField.Update that takes a boolean parameter. That will push the changes to lists that use the field.. I.e., in Powershell:
    below is the powershell script for the same
    function Update-CalcField([string]$url, [string]$Field, [string]$OutputType, [string]$Formula) {
    Add-PSSnapin Microsoft.SharePoint.PowerShell -erroraction SilentlyContinue
    Start-SPAssignment -Global
    $SPSite = New-Object Microsoft.SharePoint.SPSite($url)
    $OpenWeb = $SPsite.OpenWeb()
    $OpenWeb.Fields[$Field].OutputType = $OutputType
    $OpenWeb.Fields[$Field].Formula = $Formula
    $OpenWeb.Fields[$Field].Update($true)
    $OpenWeb.Dispose()
    $SPSite.Dispose()
    }
    #using the above function
    Update-CalcField http://santosh.kondapalli.com/sites/test -Field MyCalc -OutputType Text -Formula ‘=IF(ISBLANK([title]),”Null”,”Completed”)’
    Thanks,
    Sandy

  1. Sharepoint – Propagate site column to list. – Minh's Sharing Corner

Leave a comment