Wrox Home  
Search
Professional SharePoint 2007 Development
by John Holliday, John Alexander, Jeff Julian, Eli Robillard, Brendon Schwartz, Matt Ranlett, J. Dan Attis, Adam Buenz, Thomas Rizzo
June 2007, Paperback


Defining SharePoint Content Types in Code

There are many advantages to using the Windows SharePoint Services 3.0 object model instead of XML to define content types. These advantages include:

  • No need to refer to the GUIDs of built-in site columns
  • The ability to create dynamic types that depend on runtime conditions
  • The ability to build a library of reusable content type components

Automatic Resolution of Built-In Field Identifiers

Setting up field references for content types declared using XML requires that the unique field identifier is known ahead of time. When creating field references in code, you only need to supply the associated field name. SharePoint retrieves the identifier automatically.

For example, the following code segment creates a Project Proposal content type based on the built-in Document type, and then adds an Author column to the new content type. The Author column is provided by SharePoint as one of the built-in site columns available in the Document Columns group.

using (SPSite site = new SPSite("http://localhost")) {
	using (SPWeb web = site.OpenWeb()) {
		SPContentType baseType =  web.AvailableContentTypes["Document"];
		SPContentType proposal = new SPContentType(
			baseType, web.ContentTypes, "Project Proposal");
		web.ContentTypes.Add(proposal);
		proposal.FieldLinks.Add(new SPFieldLink(web.AvailableFields["Author"]));
	}
}

Dynamic Content Type Definitions

With XML content type definitions, the fields are declared statically at design time. Once the content type is deployed and provisioned, its fields cannot be changed without rewriting the solution. On the other hand, by using the object model, you can setup the content type differently depending on external conditions. This way you can build smarter solutions that adjust automatically to accommodate changes in the runtime environment.

Building a Library of Reusable Content Type Components

When working with the Windows SharePoint Services 3.0 object model, it is useful to create a set of helper components to simplify solution development. This can greatly reduce the steps needed to build a solution because the low-level details of working with the object model are tucked away inside higher level abstractions that are easier to declare and use. This is especially important when building document management solutions based on content types because you ultimately want to encapsulate the business rules within the content type itself. Having a library of core components means you don't have to start from scratch each time you need a new content type.

Listing 1 shows a generic ContentType class that is used as a wrapper for the underlying SPContentType object instance.

Listing 1: A Generic Content Type Wrapper Class
using System;
using Microsoft.SharePoint;
namespace ProSharePoint2007
{
	/// <summary>
	/// A utility class for manipulating SharePoint content types.
	/// </summary>
	public class ContentType
	{
		SPContentType m_contentType = null;
		/// <summary>
		/// Default constructor.
		/// </summary>
		public ContentType()
		{
		}
		/// <summary>
		/// Creates a wrapper for an existing content type instance.
		/// </summary>
		/// <param name="contentType"></param>
		public ContentType(SPContentType contentType)
		{
			m_contentType = contentType;
		}
		/// <summary>
		/// Adds a content type to a SharePoint list.
		/// </summary>
		public static void AddToList(SPList list, SPContentType contentType)
		{
			list.ContentTypesEnabled = true;
			list.ContentTypes.Add(contentType);
			list.Update();
		}
		/// <summary>
		/// Removes a content type from a SharePoint list.
		/// </summary>
		public static void RemoveFromList(SPList list, string contentTypeName)
		{
			foreach (SPContentType type in list.ContentTypes) {
				if (type.Name == contentTypeName) {
					list.ContentTypes.Delete(type.Id);
					list.Update();
					break;
				}
			}
		}
		/// <summary>
		/// Loads a pre-existing content type.
		/// </summary>
		public virtual SPContentType Create(SPWeb web, string typeName)
		{
			try {
				m_contentType = web.AvailableContentTypes[typeName];
			} catch {
			}
			return m_contentType;
		}
		/// <summary>
		/// Creates a new content type.
		/// </summary>
		public virtual SPContentType Create(SPWeb web, string typeName, 
							string baseTypeName, 
							string description)
		{
			try {
				SPContentType baseType = (baseTypeName == null 
					|| baseTypeName.Length == 0) ?
					web.AvailableContentTypes[SPContentTypeId.Empty] :
					web.AvailableContentTypes[baseTypeName];
				m_contentType = new SPContentType(
					baseType, web.ContentTypes, typeName);
				m_contentType.Description = description;
				web.ContentTypes.Add(m_contentType);
			} catch {
			}
			return m_contentType;
		}
		/// <summary>
		/// Conversion operator to access the underlying SPContentType instance.
		/// </summary>
		public static implicit operator SPContentType(ContentType t){
			return t.m_contentType;
		}

		#region Field Methods
		/// <summary>
		/// Adds a new field having a specified name and type.
		/// </summary>
		public SPField AddField(string fieldDisplayName, 
						SPFieldType fieldType, bool bRequired)
		{
			SPField field = null;
			try {
				// get the parent web
				using (SPWeb web = m_contentType.ParentWeb) {
					// create the field within the target web
					string fieldName = 
						web.Fields.Add(fieldDisplayName, 
								fieldType, bRequired);
					field = web.Fields[fieldName];
					// add a field link to the content type
					m_contentType.FieldLinks.Add(
						new SPFieldLink(field));
					m_contentType.Update(false);
				}
			} catch {
			}
			return field;
		}
		/// <summary>
		/// Adds a new field based on an existing field in the parent web.
		/// </summary>
		public SPField AddField(string fieldName)
		{
			using (SPWeb web = m_contentType.ParentWeb) {
				SPField field = web.AvailableFields[fieldName];
				try {
					if (field != null) {
						m_contentType.FieldLinks.Add(
							new SPFieldLink(field));
						m_contentType.Update(false);
					}
				} catch {
				}
			}
			return field;
		}
		#endregion
	}
}

With this helper class in the component library, it's easy to declare a new project proposal content type. It can be instantiated either from an XML definition associated with a feature, or it can be created entirely in code. Listing 2 shows the declaration for the project proposal type derived from our generic content type wrapper class.

Listing 2: Using the Content Type Wrapper Class
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
namespace ProSharePoint2007
{
	/// <summary>
	/// A helper class that encapsulates the ProjectProposal content type.
	/// </summary>
	class ProjectProposalType : ContentType
	{
		/// <summary>
		/// Creates the type using the XML content type definition.
		/// </summary>
		public SPContentType Create(SPWeb web)
		{
			return this.Create(web, "Project Proposal");
		}
		/// <summary>
		/// Creates the type using the SharePoint object model.
		/// </summary>
		public override SPContentType Create(SPWeb web, string typeName, 
							string baseTypeName, 
							string description)
		{
			// Call the base method to create the new type.
			SPContentType tProposal = base.Create(web, typeName, 
				baseTypeName, description);

			// Create the fields programmatically.
			if (tProposal != null) {
				// built-in fields
				AddField("Author");
				AddField("Subject");
				AddField("StartDate");
				AddField("EndDate");
				AddField("Status");
				AddField("Comments");
				AddField("Keywords");
				// custom fields
				AddField(Strings._Field_ProposalType);
				AddField(Strings._Field_EstimatedCost);
				AddField(Strings._Field_BidAmount);
				AddField(Strings._Field_EstimatedHours);
				AddField(Strings._Field_HourlyRate);
			}
			return tProposal;
		}
	}
}

This code produces the content type definition shown in Figure 3.


Figure 3

In order to use the content type in a SharePoint site, you must deploy the type definition and then attach it to a list or document library for which content types have been enabled. Before you can achieve this, you need an additional piece of helper code to setup the document library to hold the proposal documents.

Listing 3 shows a ProposalLibrary class created for this purpose. When creating the document library, you remove the default Document content type so that users cannot create or upload standard documents. Finally, you create a new instance of the ProjectProposal content type and add it to the document library using the AddToList static method of the ContentType helper class.

Listing 3: A Custom Proposal Document Library Class
/// <summary>
	/// A class that represents the proposals document library.
	/// </summary>
	class ProposalDocumentLibrary
	{
		SPDocumentLibrary m_docLib = null;
		public ProposalDocumentLibrary(SPWeb web)
		{
			try {
				SPListTemplate template = 
					web.ListTemplates["Document Library"];
				System.Guid guid = 
					web.Lists.Add(
						Strings._ProposalLibrary_Title,
						Strings._ProposalLibrary_Desc,
						template);
				m_docLib = web.Lists[guid] as SPDocumentLibrary;
			} catch {
			}
			// Initialize the base library properties.
			m_docLib.OnQuickLaunch = true;
			m_docLib.EnableVersioning = true;
			m_docLib.EnableModeration = true;
			m_docLib.EnableMinorVersions = true;
			// Remove the default "Document" content type.
			ContentType.RemoveFromList(m_docLib, "Document");
			// Add the custom proposal content type.
			ContentType.AddToList(m_docLib, new ProjectProposalType().Create(web));
		}
	}

The easiest way to deploy a new content type is to include it as part of a custom feature. Here, you create a ProposalManagement feature to enable all of the proposal management tools on a site. As part of the feature implementation, you create an SPFeatureReceiver class for the FeatureActivated event that handles the deployment details for our custom content types. Listing 4 illustrates this process.

Listing 4: Provisioning a Content Type upon Feature Activation
using System;
using System.Runtime.InteropServices;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebPartPages;
namespace ProSharePoint2007
{
	[Guid("63d38c9c-3ada-4e07-873f-a278443e910c")]
	partial class <b>ProposalManagerFeature : SPFeatureReceiver</b>
	{
		[SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
		public override void <b>FeatureActivated</b>(
					SPFeatureReceiverProperties properties)
		{
			if (properties == null) {
				return;
			}
			SPWeb web = properties.Feature.Parent as SPWeb;
			// Create a library to hold the proposals and add a
			// default list view to the left web part zone.
			AddListViewWebPart(web, 
				new ProposalDocumentLibrary(web), 
				"Left", PartChromeType.Default);
		}
		/// <summary>
		/// Creates a ListViewWebPart on the main page.
		/// </summary>
		private void AddListViewWebPart(SPWeb web, SPList list, 
							string zoneId, 
							PartChromeType chromeType)
		{
			// Access the default page of the web.
			SPFile root = web.RootFolder.Files[0];
			// Get the web part collection for the page.
			SPLimitedWebPartManager wpm = root.GetLimitedWebPartManager(
			System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared);
			// Add a list view to the bottom of the zone.
			ListViewWebPart part = new ListViewWebPart();
			part.ListName = list.ID.ToString("B").ToUpper();
			part.ChromeType = chromeType;
			wpm.AddWebPart(part, zoneId, 99);
		}
}

Now you have a site definition that includes the ProposalManager feature. When a site is created based on this site definition, the FeatureActivated event receiver creates a document library called Project Proposals that is automatically associated with our Project Proposal content type. Figure 4 shows the home page of a site created from the site definition.


Figure 4

This article is a pre-publication excerpt from Chapter 11, "Building Document Management Solutions," of Professional SharePoint 2007 Development (Wrox, 2007, ISBN: 978-0-470-11756-9), by John Holliday, John Alexander, Jeff Julian, Eli Robillard, Brendon Schwartz, Matt Ranlett, J. Dan Attis, Adam Buenz, and Tom Rizzo. John Holliday is an independent consultant and Microsoft MVP for Office SharePoint Server and has over 25 years of professional software development and consulting experience. John has been involved in a broad spectrum of commercial software development projects ranging from retail products to enterprise information systems for the Fortune 100. His expertise includes all aspects of distributed systems development, with a special emphasis on document automation, collaboration and enterprise content management. In addition to his professional career, John is actively engaged in humanitarian activities through Works of Wonder International, a non-profit he co-founded with his wife Alice, and the Art of Living Foundation, an international service organization devoted to uplifting human values throughout the world.