Creating Content Type Metadata for SharePoint 2007 Document Management Solutions

Excerpt from Professional SharePoint 2007 Development

By John Holliday

The term "Document Management" has become a catch-all phrase for anything having to do with documents in an enterprise setting. It is an overly broad term that covers many different aspects of managing documents; from access control to version control to auditing, review and approval of content. To understand what document management means in the SharePoint environment, it helps to consider the evolution of document management systems over the last decade or so. It also helps to appreciate the value that SharePoint provides as a development platform for document management solutions.

Early document management systems were focused primarily on keeping track of revisions to documents that involved multiple authors, and operated in a manner similar to source code control systems. Individual authors checked out documents, thereby locking them so that other authors could not overwrite their changes. System administrators could specify who had permission to view or edit documents, and could generate reports of document activity. Other functions included the ability to automatically number each major or minor revision and revert at any time to a specific version of the document, generating the final content from information stored within the database.

The notion of metadata became a key characteristic of legacy document management systems. Metadata is information about a document, as opposed to the document content itself. For example, the current version number is an example of metadata, since it is information about the document. Other examples are the title, subject, comments and keywords associated with the document.

Most document management systems store document metadata in a central database. In fact, many of the early document management systems were written as database applications. This worked well at a time when the only business process being modeled was the generic document revision cycle. It starts to break down, however, when you want to model other business processes.

This is where SharePoint emerges as a superior platform for developing document management solutions. SharePoint refines the notion of document metadata to distinguish between system, class and instance metadata. System-level metadata is maintained internally by SharePoint for all documents. Class-level metadata is stored within the SharePoint database for a given document library or content type and can be customized easily to include domain-specific information. Instance-level metadata is stored within each document instance as a set of document properties, and moves along with the physical document. This is especially important for managing documents in disconnected environments.

Defining Metadata Using Content Types

Metadata is the fuel that drives document management in SharePoint 2007, and the best way to work with document metadata is to define a content type. There are many benefits to using content types; the main one being that content types allow us to specify the custom fields needed to manage a document as it moves through the different stages of its lifecycle.

Solution developers are used to working with classes and objects, properties and methods; where each class defines the properties and methods for instances of that class. They then create objects to represent instances of each class and invoke methods on those objects to apply business rules that retrieve or modify the state of the properties associated with each instance. Building document management solutions will be much easier if you can map the core elements (document, metadata, repository, etc.) onto familiar abstractions like class and object that you are used to working with.

SharePoint 2007 content types provide just such an abstraction. The content type acts as a sort of document class, defining the columns and event receivers that comprise each instance. The columns are like properties and the event receivers are like methods. Take it one step further and say that the

ItemAdding

event receiver acts as a

constructor

and the

ItemDeleting

event receiver acts as a destructor for each document instance.

The first step in defining a new content type is to determine from which of the built-in content types to derive the new content type. In object-oriented terms, you are choosing the base class for the new content type. SharePoint includes a number of default content types; all derived from the System content type, which serves as the root of the content type hierarchy.

Figure 1 shows some of the default content types and their identifiers:


Figure 1

SharePoint employs a special numbering scheme for identifying each content type, which it uses as a shortcut for creating new content type instances. Without such a numbering scheme, it might have been prohibitive to enable content type inheritance, since SharePoint would have needed to search through the database trying to resolve content type dependencies. This way, it only needs to examine the identifier, which it reads from right to left. For example, the Picture content type identifier is 0×010102, which SharePoint reads as id 02 (Picture) derived from Document (0×0101) derived from Item (0×01) derived from System (0x).

For custom content types that you define yourself, the identifier includes a suffix, which is the GUID associated with our type, separated by 00 as a delimiter. For example, look at an example content type id 0x0101004A257CD7888D4E8BAEA35AFCDFDEA58C. Again, reading from right to left, you have 4A257CD7888D4E8BAEA35AFCDFDEA58C derived from Document (0×0101) derived from Item (0×01) derived from System (0x). The 00 serves as a delimiter between the GUID and the rest of the identifier, as shown in figure 2.


Figure 2

Each content type references a set of columns (also called fields), which comprise the metadata associated with the type. It is important to note that content types do not declare columns directly. Instead, each content type includes column references that specify the identifiers of columns declared elsewhere within the SharePoint site. Column references are declared in XML using

FieldRef

elements.

Our project proposal content type is based on the built-in Document content type, which provides the following metadata fields:

  • Name – The name of the file that contains the document content
  • Title – The title of the document (inherited from the Item content type)

Next, you select from the built-in SharePoint fields to capture the common elements of a project proposal.

  • Author (Text) – The author of the proposal
  • Start Date (DateTime) – The date on which the project will start
  • End Date (DateTime) – The date on which the project will end
  • Status (Choice) – The current document status
  • Comments (Note) – Additional comments
  • Keywords (Text) – Keywords

In addition to the built-in columns, you need a few additional columns to complete the type definition.

  • ProposalType (Choice) – The kind of proposal
  • EstimatedCost (Currency) – The total cost of the proposed work
  • BidAmount (Currency) – The proposed amount of the bid
  • EstimatedHours (Number) – The total number of hours
  • HourlyRate (Currency) – The proposed hourly rate

SharePoint provides two methods for declaring content types; using XML or using the Windows SharePoint Services object model. In actual practice, a hybrid approach is often useful. This is because while XML makes it easier to declare fields and other elements at a high level it also makes it harder to work with the content type from elsewhere in our solution. Once the essential elements have been identified, the object model provides more control over how those elements are used and how they interact with one another. What you need is an easy way to declare the type while preserving our ability to add enhanced functionality through code.

In Chapter 11, "Building Document Management Solutions," of Professional SharePoint 2007 Development (Wrox, 2007, ISBN: 978-0-470-11756-9), I present sections exploring both methods. This article only explores creating content type through code.

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.

Tags:

Comments

One Response to “Creating Content Type Metadata for SharePoint 2007 Document Management Solutions”

  1. Anonymous says:

    AddField(Strings._Field_ProposalType); AddField(Strings._Field_EstimatedCost); AddField(Strings._Field_BidAmount); AddField(Strings._Field_EstimatedHours); AddField(Strings._Field_HourlyRate);
    the code above is not working , do i have to reference any assembly?

Leave a Reply

What is 8 + 10 ?
Please leave these two fields as-is:
IMPORTANT! To be able to proceed, you need to solve the following simple math (so we know that you are a human) :-)