Programmatically creating Site Columns and Content Types using the App Model


Creating Site Columns

When provisioning custom SharePoint solutions often the first step is to create custom Site Columns. In order to create Site Columns your app needs the Manage permission for the particular Site Collection. Although Site Columns can be created in a few ways using the Client-Side Object Model (CSOM), it’s probably the easiest to use the AddFieldAsXml method:

Web rootWeb = clientContext.Site.RootWeb;

// Mind the AddFieldOptions.AddFieldInternalNameHint flag
rootWeb.Fields.AddFieldAsXml("<Field DisplayName='Session Name' Name='SessionName' ID='{2d9c2efe-58f2-4003-85ce-0251eb174096}' Group='SharePoint Saturday 2014 Columns' Type='Text' />", false, AddFieldOptions.AddFieldInternalNameHint);
rootWeb.Fields.AddFieldAsXml("<Field DisplayName='Session Presenter' Name='SessionPresenter' ID='{abf2bde8-f99b-4f76-89d0-1cb5f19695b8}' Group='SharePoint Saturday 2014 Columns' Type='Text' />", false, AddFieldOptions.AddFieldInternalNameHint);

clientContext.ExecuteQuery();

Using the AddFieldAsXml method offers you a good combination of platform hygiene and overview: you can describe the Site Column you want to create using XML yet have no reference to a declarative artifact. When provisioning a Site Column using the AddFieldAsXml method, SharePoint allows you to specify the internal name for your Site Column using the Name attribute. This is very useful as it allows you to easily reference your Site Columns in your solution. For this to work however, you must specify the AddFieldOptions.AddFieldInternalNameHint flag as the second parameter of the AddFieldAsXml method. Without it, SharePoint will ignore the Name attribute and will generate the internal name from the display name specified in the DisplayName attribute.

Creating Managed Metadata Site Columns

Creating Managed Metadata Site Columns requires a slightly different approach than creating all other Site Columns. The reason for this is, that Managed Metadata Columns need additional information such as the associated Term Store and Term Set. Unfortunately there is no API for creating Managed Metadata which is why they need to be created in two steps: first a new Site Column of type Managed Metadata is created using the regular approach. Then the newly created Site Column is casted to a Managed Metadata field and the specific properties can be set:

Web rootWeb = clientContext.Site.RootWeb;

// Create as a regular field setting the desired type in XML
Field field = rootWeb.Fields.AddFieldAsXml("<Field DisplayName='Session Topics' Name='SessionTopics' ID='{bed14299-afe0-4c75-9e04-92e3d8b39a18}' Group='SharePoint Saturday 2014 Columns' Type='TaxonomyFieldTypeMulti' />", false, AddFieldOptions.AddFieldInternalNameHint);
clientContext.ExecuteQuery();

Guid termStoreId = Guid.Empty;
Guid termSetId = Guid.Empty;
GetTaxonomyFieldInfo(clientContext, out termStoreId, out termSetId);

// Retrieve as Taxonomy Field
TaxonomyField taxonomyField = clientContext.CastTo<TaxonomyField>(field);
taxonomyField.SspId = termStoreId;
taxonomyField.TermSetId = termSetId;
taxonomyField.TargetTemplate = String.Empty;
taxonomyField.AnchorId = Guid.Empty;
taxonomyField.Update();

clientContext.ExecuteQuery();

The information about the associated Term Store and Term Set can be easily retrieved from the current Client Context:

private void GetTaxonomyFieldInfo(ClientContext clientContext, out Guid termStoreId, out Guid termSetId) {
    termStoreId = Guid.Empty;
    termSetId = Guid.Empty;

    TaxonomySession session = TaxonomySession.GetTaxonomySession(clientContext);
    TermStore termStore = session.GetDefaultSiteCollectionTermStore();
    TermSetCollection termSets = termStore.GetTermSetsByName("SPSNL14", 1033);

    clientContext.Load(termSets, tsc => tsc.Include(ts => ts.Id));
    clientContext.Load(termStore, ts => ts.Id);
    clientContext.ExecuteQuery();

    termStoreId = termStore.Id;
    termSetId = termSets.FirstOrDefault().Id;
}

First, using the current ClientContext, we establish a session with the Managed Metadata Service. Next we retrieve the Term Store associated with the current Site Collection. Next we retrieve the Term Set using its name. Finally we load both the Term Store and Term Set and their IDs and return them to the caller. Having retrieved all the necessary information for our Managed Metadata field to work, we cast it to the TaxonomyField type. This gives us access to the properties on which the necessary information about the Managed Metadata field must be set. We finalize the configuration by setting the values on the properties and submitting our changes.

Creating Content Types

Once the Site Columns are created the next step is to provision Content Types. Using CSOM you can provision Content Types in two ways: you can provide them with a predefined ID or you can have SharePoint create an ID for you:

Web rootWeb = clientContext.Site.RootWeb;

// create by ID
rootWeb.ContentTypes.Add(new ContentTypeCreationInformation {
    Name = "Session",
    Id = "0x0100BDD5E43587AF469CA722FD068065DF5D",
    Group = "SharePoint Saturday 2014 Content Types"
});
clientContext.ExecuteQuery();

// create by reference
var itemContentTypes = clientContext.LoadQuery(rootWeb.ContentTypes.Where(ct => ct.Name == "Item"));
clientContext.ExecuteQuery();

var itemContentType = itemContentTypes.FirstOrDefault();

if (itemContentType != null) {
    rootWeb.ContentTypes.Add(new ContentTypeCreationInformation {
        Name = "Session Evaluation",
        Group = "SharePoint Saturday 2014 Content Types",
        ParentContentType = itemContentType
    });
    clientContext.ExecuteQuery();
}
else {
    throw new InvalidOperationException("Item Content Type not found");
}

When creating a Content Type with a predefined ID you have to set the value of the ID property in the ContentTypeCreationInformation object. This ID should be a valid Content TypeID (ID of the parent Content Type followed by 00 and a GUID without dashes or two hexadecimal values). If you don’t care about the ID of your Content Type, or want to ensure that you will get a unique ID, you can have SharePoint generate the ID for you. In that case you have to retrieve the parent Content Type and pass it to the ParentContentType property of the ContentTypeCreationInformation object. When creating a Content Type using CSOM you can either set the ID of the ParentContentType property of the ContentTypeCreationInformation object. If you try to set both or neither you will get an exception.

Adding existing Site Columns to Content Types

The last step is to tie the newly created assets together by adding the newly created Site Columns to the newly created Content Types. The first step is to retrieve the Site Columns and Content Types you wish to link. Depending on how you provisioned them you can retrieve them either by their names or IDs:

Web rootWeb = clientContext.Site.RootWeb;

Field session = rootWeb.Fields.GetByInternalNameOrTitle("SessionName");
ContentType sessionContentType = rootWeb.ContentTypes.GetById("0x0100BDD5E43587AF469CA722FD068065DF5D");

sessionContentType.FieldLinks.Add(new FieldLinkCreationInformation {
    Field = session
});
sessionContentType.Update(true);
clientContext.ExecuteQuery();

Once you have both references, you call the Add method on the FieldLinks property of the Content Type. Using the FieldLinkCreationInformation object you can pass the reference to the Site Column that should be added to the Content Type. When updating the Content Type you can choose whether you want to push changes to other Content Types that inherit from this Content Type or not, which works exactly as when using server-side API. Download: Sample App for SharePoint (230KB, ZIP)

Others found also helpful: