Provisioning Content Types in SharePoint 2010

Overview

Provisioning in SharePoint has always been not the simplest part, especially if it concerns the Content Type provision.

It is known that in SharePoint 2010, you can declare content types in a feature or you can create them programmatically. Programmatically created content types are more flexible and provide more granular control over how and when updates to the content type are made during upgrades. In contrast, you cannot change the declarative definition of a content type that was created by using Collaborative Application Markup Language (CAML) after the content type is activated—instead, you must update the content type programmatically.

Given the Pros and Cons, it is up to you to decide which way is better suits your needs.

How to create a field based on the specified schema

There is one method that allows to create a field based on the specified schema, see AddFieldAsXml method for details . It means that you could declare a field once (CAML), but to provision it either via feature activation or programmatically. As for me, I find this technique very useful for curtain cases. But what about content types?

For content type there is no such capability that allows to create a content type based on the specified schema. And the goal of this post is to demonstrate how it could be achieved.

How to create a content type based on the specified schema

Listing 1.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema

namespace Deployment.ContentType
{
/// <summary>
/// Content Type
/// </summary>
public static class SPContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// An instance of the new content type.
/// </returns>
/// <param name="contentTypes">Content Type collection</param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static SPContentType AddContentTypeAsXml(this SPContentTypeCollection contentTypes, string schemaXml)
{
SPContentType contentType;
using (var xrdr = new XmlTextReader(new StringReader(schemaXml)))
{
xrdr.ProhibitDtd = true;
contentType = contentTypes.CreateContentType();
LoadXmlInternal(contentType, xrdr);
contentTypes.Add(contentType);
}
return contentType;
}
/// <summary>
/// Create content type
/// </summary>
/// <param name="contentTypes"></param>
/// <returns></returns>
private static SPContentType CreateContentType(this SPContentTypeCollection contentTypes)
{
var constructor = (typeof(SPContentType)).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var contentType = (SPContentType)constructor.Invoke(new object[0]);
contentType.SetWeb(contentTypes.GetWeb());
return contentType;
}
/// <summary>
/// Load schema for content type
/// </summary>
/// <param name="contentType"></param>
/// <param name="xmlReader"></param>
private static void LoadXmlInternal(SPContentType contentType, XmlReader xmlReader)
{
var loadMethod = contentType.GetType().GetMethod("Load",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new[] { typeof(XmlReader) },
null);
loadMethod.Invoke(contentType, new object[] { xmlReader });
}
private static SPWeb GetWeb(this SPContentTypeCollection contentTypes)
{
var webProp = typeof(SPContentTypeCollection).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
return (SPWeb)webProp.GetValue(contentTypes, null);
}
private static void SetWeb(this SPContentType contentType,SPWeb web)
{
var webProp = typeof(SPContentType).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
webProp.SetValue(contentType, web, null);
}
}
}

Listing 2.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema via Managed Client Object Model

namespace Deployment.Client
{
/// <summary>
/// Creates a content type based on the specified schema (CSOM)
/// </summary>
public static class ContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// A newly created client object of a content type.
/// </returns>
/// <param name="clientContext">Client Context </param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static ContentType AddContentTypeAsXml(ClientContext clientContext, string schemaXml)
{
ContentType contentType;
using (var xmlTextReader = new XmlTextReader(new StringReader(schemaXml)))
{
xmlTextReader.ProhibitDtd = true;
contentType = Load(clientContext, clientContext.Web.ContentTypes, xmlTextReader);
}
return contentType;
}
private static ContentType Load(ClientContext clientContext, ContentTypeCollection cts, XmlReader xrdr)
{
var contentTypeCreation = new ContentTypeCreationInformation { };
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Name"))
contentTypeCreation.Name = xrdr.Value;
if (xrdr.MoveToAttribute("Group"))
contentTypeCreation.Group = xrdr.Value;
if (xrdr.MoveToAttribute("Description"))
contentTypeCreation.Description = xrdr.Value;
contentTypeCreation.ParentContentType = GetParentContentType(clientContext, xrdr.GetAttribute("ID"));
var contentType = cts.Add(contentTypeCreation);
//Additional properties
if (xrdr.MoveToAttribute("Hidden"))
contentType.Hidden = xrdr.Value == "TRUE";
bool flagReadOnly = xrdr.MoveToAttribute("ReadOnly");
contentType.ReadOnly = flagReadOnly && xrdr.Value == "TRUE";
xrdr.Read();
while (xrdr.LocalName != "ContentType")
{
switch (xrdr.LocalName)
{
case "FieldRefs":
LoadFieldLinks(clientContext, contentType, xrdr);
break;
}
if (!xrdr.Read())
break;
}
return contentType;
}
private static void LoadFieldLinks(ClientContext clientContext, ContentType contentType, XmlReader xrdr)
{
xrdr.Read();
while (xrdr.LocalName == "FieldRef" || xrdr.LocalName == "RemoveFieldRef")
{
if (xrdr.LocalName == "RemoveFieldRef")
{
xrdr.Read();
}
else
{
LoadFieldLink(clientContext, contentType.FieldLinks, xrdr);
contentType.Update(false);
clientContext.ExecuteQuery();
}
}
}
internal static FieldLink LoadFieldLink(ClientContext clientContext, FieldLinkCollection fieldLinks, XmlReader xrdr)
{
var linkCreationInfo = new FieldLinkCreationInformation();
var hostField = GetField(clientContext, xrdr);
linkCreationInfo.Field = hostField;
var fieldLink = fieldLinks.Add(linkCreationInfo);
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Required"))
{
fieldLink.Required = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
if (xrdr.MoveToAttribute("Hidden"))
{
fieldLink.Hidden = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
xrdr.Read();
if (xrdr.LocalName == "Default")
{
xrdr.Read();
}
return fieldLink;
}
/// <summary>
///
/// </summary>
/// <param name="clientContext"></param>
/// <param name="xrdr"></param>
/// <returns></returns>
private static Field GetField(ClientContext clientContext, XmlReader xrdr)
{
var field = clientContext.Web.AvailableFields.GetById(new Guid(xrdr["ID"]));
clientContext.Load(field);
clientContext.ExecuteQuery();
return field;
}
private static ContentType GetParentContentType(ClientContext clientContext, string id)
{
var ctId = new SPContentTypeId(id);
var parentCtId = ctId.Parent.ToString();
return GetContentTypeById(clientContext, parentCtId);
}
public static ContentType GetContentTypeById(ClientContext clientContext, string ctId)
{
var ct = clientContext.Web.AvailableContentTypes.GetById(ctId);
clientContext.Load(ct);
clientContext.ExecuteQuery();
return ct;
}
public static ContentType GetContentTypeByName(ClientContext clientContext, string name)
{
var cts = clientContext.Web.ContentTypes;
clientContext.Load(cts);
clientContext.ExecuteQuery();
foreach (var ct in cts)
{
if (ct.Name == name)
return ct;
}
return null;
}
}
}

The following list summarizes the limitations for Client OM approach:

  • Not the all content type attributes could be specified via Client OM
  • In particular it is not supported to specify Content Type Id, only parent Content Type could be specified

References

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.