Adding new content types is easy in CSOM using the new ContentTypeCreationInformation class. This class does pretty much all the heavy lifting for you as Vesa describes in one of the blogs in the FTC to CAM series (http://blogs.msdn.com/b/vesku/archive/2014/02/28/ftc-to-cam-create-content-types-with-specific-ids-using-csom.aspx).
This mechanism works fine for site content types, but when you try using this to provision a content type to a list something strange happens. The content type seems to inherit from its site content type parent, but all the fields from the parent are missing. To test this in an isolated environment, I created a provider-hosted app with this snippet:
protected void Page_Load(object sender, EventArgs e) { // The following code gets the client context and Title property by using TokenHelper. // To access other properties, the app may need to request permissions on the host web. var spContext = SharePointContextProvider.Current.GetSharePointContext(Context); using (var clientContext = spContext.CreateUserClientContextForSPHost()) { // load web and list clientContext.Load(clientContext.Web); clientContext.ExecuteQuery(); var list = clientContext.Web.GetList("/sites/dev-en/SitePages"); clientContext.Load(list); clientContext.ExecuteQuery(); // content type id of Image var parentContentTypeId = "0x0101009148F5A04DDD49CBA7127AADA5FB792B00AADE34325A8B49CDA8BB4DB53328F214"; EnsureContentTypeFields(clientContext, list, parentContentTypeId); var info = new ContentTypeCreationInformation { // set up new content type inheriting from Image Id = parentContentTypeId + "00" + "9F13E4D76C8843D6BAF5EACD8EE880AB", Name = "MyNewContentType", }; // add new content type to the list list.ContentTypes.Add(info); clientContext.ExecuteQuery(); } }
The result is as follows. Note that the fields from parent content type (Image) are missing in the new content type.
Debugging this on my local farm, I discovered that the fields are actually added to the list content type as expected. The problem is not that the content type is inherited incorrectly, but that fields are not pushed to the list. Since the fields are not present in the list, SharePoint hides them in the content type as well. The solution is to manually push the fields to the list prior to provisioning the content type.
protected void Page_Load(object sender, EventArgs e) { // The following code gets the client context and Title property by using TokenHelper. // To access other properties, the app may need to request permissions on the host web. var spContext = SharePointContextProvider.Current.GetSharePointContext(Context); using (var clientContext = spContext.CreateUserClientContextForSPHost()) { // load web and list clientContext.Load(clientContext.Web); clientContext.ExecuteQuery(); var list = clientContext.Web.GetList("/sites/dev-en/SitePages"); clientContext.Load(list); clientContext.ExecuteQuery(); // content type id of Image var parentContentTypeId = "0x0101009148F5A04DDD49CBA7127AADA5FB792B00AADE34325A8B49CDA8BB4DB53328F214"; EnsureContentTypeFields(clientContext, list, parentContentTypeId); var info = new ContentTypeCreationInformation { // set up new content type inheriting from Image Id = parentContentTypeId + "00" + "9F13E4D76C8843D6BAF5EACD8EE880AB", Name = "MyNewContentType", }; // add new content type to the list list.ContentTypes.Add(info); clientContext.ExecuteQuery(); } } protected void EnsureContentTypeFields(ClientContext context, List list, string contentTypeId) { // get the content type context.Load(list.ParentWeb.AvailableContentTypes); context.ExecuteQuery(); var contentType = list.ParentWeb.AvailableContentTypes.FirstOrDefault(c => c.Id.ToString() == contentTypeId); if (contentType == null) { throw new InvalidOperationException("Content type not found"); } // load the fields in the list and the fields in the content type var currentFields = list.Fields; var requiredFields = contentType.Fields; context.Load(currentFields); context.Load(requiredFields); context.ExecuteQuery(); foreach (var field in requiredFields) { var currentField = currentFields.FirstOrDefault(f => f.Id == field.Id); if (currentField == null) { // add the field currentFields.Add(field); context.ExecuteQuery(); // update the local collection context.Load(currentFields); context.ExecuteQuery(); } } }
As a result, the fields are added to the list and available in the content type.