Thursday, December 6, 2012

Programmatically Creating a Content Type, Site Column and List in SharePoint

I often find the need to create a new site collection and start throwing objects (lists, content types, etc...) onto it. There are obviously a million ways to do this, but I just thought I would share how I do it.

In this code example, I create a Feature Receiver that inherits from SPFeatureReceiver and overrides the FeatureActivated and FeatureDeactivating methods. In the FeatureActivated method, I create a new SharePoint content type that inherits from item. I then create a new list and site column. finally, I connect them all together.

Note that I use a couple of helper classes. to actually create each item. I do it often enough that I have abstracted it out into a Utility class.

This is the code for FeatureReceiver class. Note that it inherits from SPFeatureReceiver

 
public class FeatureReceiver : SPFeatureReceiver
{
}

Within the FeatureReceiver class, I override the FeatureActivated method. So, when my feature is activated on the site, it will create my content type, list, and site column

 
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
}

Within the FeatureActivated method I go through the work of creating each item and connecting them. I also set a few properties along the way.

 
SPSite site = (SPSite)properties.Feature.Parent;
using (SPWeb web = site.OpenWeb())
{
	//Create a new content type. Note that it inherits from item
	SPContentTypeId itemContentTypeId = new SPContentTypeId("0x01");
	SPContentType myContentType = Utility.CreateSiteContentType(web, "Content Type Name", itemContentTypeId, "Grouping Name for the Custom Content Type");

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myContentType.Update();
	web.AllowUnsafeUpdates = false;

	//Add a single choice field to the site as a site column
	SPField field = Utility.CreateSiteColumn(web, "My Choice Field", SPFieldType.Choice, "My Custom Fields");
	field.Title = "Type of Data";
	field.Description = "Audience : Choice field used to determine whether the requested video is for internal, external or both internal and external dissemination.";

	//Add the new field to the new content type
	Utility.AddFieldToContentType(web, myContentType.Id, field);

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myContentType.Update();
	web.AllowUnsafeUpdates = false;

	//Add a new list to the site
	Guid guid = web.Lists.Add("List Name", "List Description", SPListTemplateType.GenericList);

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	web.Update();
	web.AllowUnsafeUpdates = false;

	//Add the new content type to the new list
	SPList myList = web.Lists.GetList(guid, false);
	myList.ContentTypesEnabled = true;
	myList.ContentTypes.Add(myContentType);

	//Add a couple of event receivers to the list 
	myList.EventReceivers.Add(SPEventReceiverType.ItemAdded, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[DLL Key Token]", "[Namespace].[MethodName]");
	myList.EventReceivers.Add(SPEventReceiverType.ItemUpdating, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=pDLL Key Token]", "[Namespace].[MethodName]");

	//Change some of the properties on the list
	myList.EnableAttachments = false;
	myList.EnableFolderCreation = false;
	myList.EnableVersioning = true;
	myList.ForceCheckout = false;

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myList.Update();
	web.AllowUnsafeUpdates = false;
}

I also override the FeatureDeactivating method and clean everything up when the feature is deactivated. Depending on your environment and what you are using this for, you may not want to do this. It will delete your data.

 
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
	try
	{
		SPSite site = (SPSite) properties.Feature.Parent;
		using (SPWeb web = site.OpenWeb())
		{
			//Delete the list.
			//Note that if the list is not there
			//it throws an Argument Exception.
			//We trap it and ignore it.
			try
			{
				web.Lists["List Name"].Delete();
				web.AllowUnsafeUpdates = true;
				web.Update();
				web.AllowUnsafeUpdates = false;
			}
			catch (ArgumentException)
			{				}

			//Delete the content type. 
			//Note that if the content type is not there
			//it throws an Null Reference Exception.
			//We trap it and ignore it.
			try
			{
				web.ContentTypes["Content Type Name"].Delete();
				web.AllowUnsafeUpdates = true;
				web.Update();
				web.AllowUnsafeUpdates = false;
			}
			catch (NullReferenceException)
			{				}

			//Loop through the fields in My Custom Group
			//and delete them
			for (int i = 0; i < web.Fields.Count; i++)
			{
				if (web.Fields[i].Group == "My Custom Fields")
					web.Fields[i].Delete();
			}
		}
	}
	catch (Exception exception)
	{
		//Insert code to write the error to the log
	}
}

This utility class provides a couple of methods for creating a content type, site column and adding the site column to the content type. My actual implementation of this has several overloaded versions of each method for various purposes.

public static class Utility
{
	public static SPContentType CreateSiteContentType(SPWeb web, string contentTypeName,
		SPContentTypeId parentItemCTypeId, string group)
	{
		if (web.AvailableContentTypes[contentTypeName] == null)
		{
			SPContentType itemCType = web.AvailableContentTypes[parentItemCTypeId];
			SPContentType contentType = 
				new SPContentType(itemCType, web.ContentTypes, contentTypeName) {Group = @group};
			web.ContentTypes.Add(contentType);
			contentType.Update();
			return contentType;
		}
		return web.ContentTypes[contentTypeName];
	}

	public static SPField CreateSiteColumn(SPWeb web, string displayName,
		SPFieldType fieldType, string groupDescriptor)
	{
		if (!web.Fields.ContainsField(displayName))
		{
			string fieldName = web.Fields.Add(displayName, fieldType, false);
			SPField field = web.Fields.GetFieldByInternalName(fieldName);
			field.Group = groupDescriptor;
			field.Update();
			return field;
		}
		return web.Fields[displayName];
	}
		
	public static void AddFieldToContentType(SPWeb web,
		SPContentTypeId contentTypeId, SPField field)
	{
		SPContentType contentType = web.ContentTypes[contentTypeId];
		if (contentType == null) return;
		if (contentType.Fields.ContainsField(field.Title)) return;
		SPFieldLink fieldLink = new SPFieldLink(field);
		contentType.FieldLinks.Add(fieldLink);
		contentType.Update();
	}
}

As always comments and feedback are welcome. Enjoy!

5 comments:

  1. Hi, Doug, this is a great post, I like especially the Utility class, it solved my problem :)

    However I got a little problem: when I was trying exactly your code, it gave me "The object has been updated by another user since it was last fetched." error. I'd rather not create new SPWeb reference, so I tried commenting out the "contentType.Update();" inside the CreateSiteContentType method, and it worked.

    Because I am new to Sharepoint, I don't know whether it makes the method unstable. I'd like to know what you would recommand to do in this case.

    Thank you in advance!

    ReplyDelete
  2. Sorry, a correction: I finally changed the FeatureActivated method, because when I tried to add more fields the error appeared another time.

    I put the CreateSiteContentType and the AddFieldToContentType together, and keep only one update of the ContentType. Then it worked.

    ReplyDelete
  3. Hi Doug,
    Good blog! :) But while trying to delete content type I'm getting error "The content type is part of an application feature."
    How to deal with it? Would you please suggest me a way?

    Thanks,
    Priya

    ReplyDelete
  4. Hi,

    I have an error '... no reference and does not contain definition...' in the Add method of EventReceivers:

    myList.EventReceivers.Add(SPEventReceiverType.ItemAdded, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[DLL Key Token]", "[Namespace].[MethodName]");
    myList.EventReceivers.Add(SPEventReceiverType.ItemUpdating, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=pDLL Key Token]", "[Namespace].[MethodName]");

    Do i need to add any additional reference? so far i have these references added:
    using Microsoft.Sharepoint;
    using Microsoft.Sharepoint.Administration;

    Regards,
    rva.

    ReplyDelete
  5. First, you create the content type, then you add the site column (which you haven't save it) and then you update everything. Is this obligatory?

    ReplyDelete