Best Practices for Content Types in SharePoint Server 2007 and Windows SharePoint Services 3.0

Summary: Learn best practices to develop, deploy, and manage custom content types using Office SharePoint Server 2007 and Windows SharePoint Services 3.0 with Visual Studio 2008, and scenarios for making updates to existing content types in lists. (13 printed pages)

Scot Hillier, Critical Path Training, LLC (Microsoft MVP)

July 2009

Applies to: Microsoft Office SharePoint Server 2007, Windows SharePoint Services 3.0

Contents

  • Overview of Content Types and Development Challenges

  • Developing Content Types

  • Deploying Content Types

  • Using Content Types in Custom Lists

  • Updating Content Types

  • Conclusion

  • About the Author

  • Additional Resources

Overview of Content Types and Development Challenges

Content types are central to many aspects of functionality in Windows SharePoint Services 3.0 and Microsoft Office SharePoint Server 2007. Content types define the metadata (columns), document templates, associated workflows, and other settings for items that appear in lists and libraries. Content types can be defined centrally and reused across different lists and libraries. Because content types are central to the functionality of Windows SharePoint Services and Office SharePoint Server, they are also central to custom solutions you create. However, you can face several challenges if you want to build solutions based on content types.

Windows SharePoint Services and SharePoint Server offer predefined content types that support built-in (out-of-the-box) functionality such as task lists, calendars, wikis, and document libraries. You can view the existing set of content types by opening the Site Content Type Gallery available on the Site Settings page. Figure 1 shows the Site Content Type Gallery and several content types.

Figure 1. Site Content Type Gallery

Site Content Type Gallery

When you examine the content types in the gallery, you immediately see content types that have a 1:1 relationship with list types and libraries in Windows SharePoint Services and SharePoint Server. For example, you use the Document content type to define the elements of documents stored in libraries; you use the Task content type to define the elements of an item in a task list.

The content type infrastructure supports a form of inheritance that enables you to build a set of elements for a new content type. In Figure 1, notice that each content type lists a parent content type from which it inherits.

Parent-child relationships between content types are indicated by the identifier (ID) of the content type. When a content type inherits from an existing content type, the ID of the child is formed as the ID of the parent followed by unique characters. Table 1 shows some of the built-in content types, their IDs, and their parent-child relationships.

Table 1. Content type hierarchy

System (0x)

Item (0x01)

Document (0x0101)

Form (0x010101)

Picture (0x010102)

Event (0x0102)

Issue (0x0103)

Announcement (0x0104)

Link (0x0105)

Contact (0x0106)

Message (0x0107)

Task (0x0108)

Content type inheritance is similar to the inheritance model that you use when creating classes, but it is not truly object-oriented. For example, changes to parent content types are not automatically reflected in child content types. Changes to parent content types must be "pushed-down" to child content types manually or through code. Additionally, you can make changes to child content types that can affect settings in the parent content type.

The concept of pushing down changes is one of the main challenges for developers who need to manage versions of solutions based on content types. This situation arises when a new version of a content type is deployed for an existing solution; you must determine whether the new solution should push down changes to child types, which can result in overwriting some settings in the child content type.

Because content types are centrally defined, they may be reused in any subsite below the Site Content Type Gallery. This reuse model is how, for example, the Task content type can be defined at the top-level site in a portal and used in every task list throughout that portal. As a result, every task list supports the same fields such as Priority, Assigned To, and % Complete.

The reuse model, however, suffers from the same limitation as the inheritance model; changes to the top-level site content type are not reflected automatically in the lists and libraries that use it. Instead, updates to the child lists and libraries are made only when changes to the top-level site content type are pushed down manually or through code. This is because lists do not actually use the top-level site content type directly. Instead, they are provided a copy of the site content type known as a list content type.

List content types function independently from site content types. They can be updated separately by simply adding a column directly to a list. This sets up a situation in which the list content type can deviate significantly from the site content type. Changes to the site content type that are subsequently pushed down to the list content type can overwrite changes made in the list content type. This is another challenge facing developers who are creating solutions based on content types.

Developing Content Types

When creating a custom solution based on content types, you can define columns, document templates, workflows, document information panels, and information policy settings for their custom content types. For this discussion, however, the focus is on creating columns and document templates for content types. Columns and document templates form the foundation of most content types, and they show well the challenges you face when creating, deploying, and updating content types.

An example scenario could be developing a set of content types for managing financial documents. The concept is to use the financial content types as the basis for creating a Financial Documents Library solution. This solution will represent a new type of library specifically for managing financial documents. Table 2 shows the custom content types that you would create, and how they inherit from the built-in Document content type.

Table 2. Custom content type hierarchy

Document

Financial Document

Purchase Order

Invoice

The Financial Document content type is the base content type in the custom set. The Financial Document content type inherits from the built-in Document content type, which makes sense because you want the financial documents to have the same behavior as a base document. The Invoice and Purchase Order content types inherit from the Financial Document content type and represent specific types of financial documents. End users of the solution will create the Invoice and Purchase Order content types. Figure 2 shows the completed solution.

Figure 2. Financial Documents library

Financial Documents library

Defining Site Columns

In much the same way that Windows SharePoint Services and SharePoint Server support the central management of content types, they also support the central management of column definitions through the site column infrastructure. The site column infrastructure enables columns to be defined at the top-level site and be reused in any subsite. Typically, developers create site columns and then use those columns within their custom content types. You can view the existing set of site columns by opening the Site Column Gallery available on the Site Settings page. Figure 3 shows the Site Column Gallery with the core document columns.

Figure 3. Site Column Gallery

Site Column Gallery

Because the development of site columns is typically the first step in the development of site content types, you can begin by examining techniques for creating site columns. Developers can create site columns in their solutions by using either declarative XML or by using the SharePoint API. Declarative XML uses Collaborative Application Markup Language (CAML) and is defined in the element manifest file of a Feature. The SharePoint API is generally accessed through a feature receiver class. Typically, declarative XML is somewhat easier to create initially. However, declarative XML generally offers less flexibility than using the SharePoint API, which has access to the capabilities of the entire Microsoft .NET Framework and can be debugged at run time.

To create site columns with declarative XML, simply include a Field element for each site column. The Field element enables you to specify the name, data type, and other settings for the site column. The ID attribute is the unique identifier for the field and is a GUID generated from within Microsoft Visual Studio 2008. The following example shows how the Amount column is defined.

<Field
  ID="{D713DCD2-6626-47a1-A23F-3C24CD66A3F9}" 
  Name="Amount"
  DisplayName="Amount"
  Type="Currency"
  Decimals="2"
  Min="0" 
  Group="Financial Columns">
</Field>

To create site columns by using the SharePoint API, you must create a feature receiver assembly that inherits from the SPFeatureRecever class. Then, you can create the site columns programmatically in the FeatureActivated event. The following example shows how you create the Amount column in code.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
  SPSite siteCollection = (SPSite)properties.Feature.Parent;
  SPWeb site = siteCollection.OpenWeb();

  string amountFieldName = site.Fields.Add("Amount", SPFieldType.Currency, false);
  SPFieldCurrency amountField = 
    (SPFieldCurrency)site.Fields.GetFieldByInternalName(amountFieldName);
  amountField.Group = "Financial Columns";
  amountField.Update();
}

Defining Content Types

After you define the site columns, the next step is to use them to begin defining the content types. As with site columns, you can define content types by using declarative XML or the SharePoint API. If you use declarative XML, you can declare the content types in the same file as the site columns. If you use the SharePoint API, you can write the code in the same FeatureActivated event where the site columns are created.

To create content types with declarative XML, include a ContentType element for each content type. The ContentType element must have an ID attribute that specifies its parent, but that is also globally unique. The best practice for creating content type IDs is to begin with the ID of the parent content type, append two zeroes, and then append a GUID with all of the punctuation removed. This approach ensures that your content type has a unique ID. Additional custom content types that inherit from your content type can simply append "01", "02", and so on. Table 3 shows the inheritance chain for the financial content types, including the IDs.

Table 3. Custom content type hierarchy with IDs

Document

0x0101

Financial Document

0x01010012841A8869DB425cB829C3875EC558CE

Purchase Order

0x01010012841A8869DB425cB829C3875EC558CE01

Invoice

0x01010012841A8869DB425cB829C3875EC558CE02

The ContentType elements must also include FieldRef elements that reference the ID of the site columns to include in the content type. This structure is required because content types do not actually contain site columns. Instead, content types reference the site column definitions. The following example shows a partial definition for the Financial Document content type that references the Amount site column.

<ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE"
  Name="Financial Document"
  Description="Base financial document"
  Version="0"
  Group="Financial Document Types" >
  <FieldRefs>
   <FieldRef 
    ID="{D713DCD2-6626-47a1-A23F-3C24CD66A3F9}" 
    Name="Amount" DisplayName="Amount" />
  </FieldRefs>
</ContentType>

When content types are created by using the SharePoint API, they are created in the SPFeatureReceiver class immediately following the definition of the site columns. The following example shows how to create the Financial Document content type, including a reference to the Amount field.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
  SPSite siteCollection = (SPSite)properties.Feature.Parent;
  SPWeb site = siteCollection.OpenWeb();

  string amountFieldName = site.Fields.Add("Amount", SPFieldType.Currency, false);
  SPFieldCurrency amountField = 
    (SPFieldCurrency)site.Fields.GetFieldByInternalName(amountFieldName);
  amountField.Group = "Financial Columns";
  amountField.Update();

  // Get the parent document content type.
  SPContentTypeId documentCTypeId = new SPContentTypeId("0x0101");
  SPContentType documentCType = 
    site.AvailableContentTypes[documentCTypeId];

  // Financial Document content type.
  SPContentType financialDocumentCType = new 
    SPContentType(documentCType,
                  site.ContentTypes,
                  "Financial Document");
  financialDocumentCType.Group = "Financial Content Types";
  site.ContentTypes.Add(financialDocumentCType);
}

Finalizing the Definitions

After the content types are created and referencing the site columns, you perform additional configuration of the content type. Often, you will want your content types to have a document template for use whenever a new instance of the content type is created. In our scenario, you want templates for both purchase orders and invoices. Just as before, you can choose to create the document templates through declarative XML or through the SharePoint API.

When you use declarative XML to specify a document template, the template must be loaded into the site by using a Module element. The Module element specifies where to load the document template for the content type. Windows SharePoint Services and SharePoint Server keep the document template for a site content type under a folder named _cts in a subfolder that has the same name as the content type. The content type definition can then reference the template by using the DocumentTemplate element. Listing 1 shows the complete XML definition of the financial content types.

Listing 1. XML definition for the financial content types

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="https://schemas.microsoft.com/sharepoint/">
 <Module Name="PurchaseOrderDocumentTemplate"
         Url="_cts/Purchase Order" RootWebOnly="TRUE">
  <File Url="PurchaseOrder.docx" Type="Ghostable" />   
 </Module>
 <Module Name="InvoiceDocumentTemplate"
         Url="_cts/Invoice" RootWebOnly="TRUE">
  <File Url="Invoice.docx" Type="Ghostable" />
 </Module>
 <Field
  ID="{D713DCD2-6626-47a1-A23F-3C24CD66A3F9}" Name="Amount"
  DisplayName="Amount" Type="Currency" Decimals="2"
  Min="0" Group="Financial Columns">
 </Field>
 <Field
  ID="{8AE47811-8E98-4f49-AAE8-CF52A4BF83AB}" Name="DepartmentName"
  DisplayName="Department" Type="Choice" Group="Financial Columns">
  <CHOICES>
   <CHOICE>Administration</CHOICE>
   <CHOICE>Information Services</CHOICE>
   <CHOICE>Facilities</CHOICE>
   <CHOICE>Operations</CHOICE>
   <CHOICE>Sales</CHOICE>
   <CHOICE>Marketing</CHOICE>
  </CHOICES>
 </Field>
 <Field
  ID="{E9FC44C2-9036-490d-9D7C-599A9366779F}" Name="ClientName"
  DisplayName="Client Name" Type="Text" Group="Financial Columns">
 </Field>
 <ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE"
  Name="Financial Document"
  Description="Base financial document"
  Version="0" Group="Financial Document Types" >
  <FieldRefs>
   <FieldRef 
    ID="{D713DCD2-6626-47a1-A23F-3C24CD66A3F9}" 
    Name="Amount" DisplayName="Amount" />
  </FieldRefs>
 </ContentType>
 <ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE01"
  Name="Purchase Order" Version="0"
  Group="Financial Document Types">
  <DocumentTemplate TargetName="PurchaseOrder.docx" />
  <FieldRefs>
   <FieldRef
    ID="{8AE47811-8E98-4f49-AAE8-CF52A4BF83AB}"
    Name="DepartmentName" DisplayName="Department" />
  </FieldRefs>
 </ContentType>
 <ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE02"
  Name="Invoice"
  Version="0"
  Group="Financial Document Types">
  <DocumentTemplate TargetName="Invoice.docx" />
  <FieldRefs>
   <FieldRef 
    ID="{E9FC44C2-9036-490d-9D7C-599A9366779F}" 
    Name="ClientName" DisplayName="Client Name" />
  </FieldRefs>
 </ContentType>
</Elements>

When you use the SharePoint API to specify a document template, the template must be loaded into the site from the file system. The document template is still stored in the same location and the content type definition must then reference it through the DocumentTemplate property. Listing 2 shows the complete code for the financial content types.

Listing 2. Feature receiver for the financial content types

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
  SPSite siteCollection = (SPSite)properties.Feature.Parent;
  SPWeb site = siteCollection.OpenWeb();

  /* CREATE SITE COLUMNS */

  // Amount.
  string amountFieldName = site.Fields.Add("Amount", SPFieldType.Currency, false);
  SPFieldCurrency amountField =
    (SPFieldCurrency)site.Fields.GetFieldByInternalName(amountFieldName);
  amountField.Group = "Financial Columns";
  amountField.Update();

  // Department Name.
  string departmentName =
    site.Fields.Add("Department Name", SPFieldType.Choice, false);
  SPFieldChoice departmentField =
    (SPFieldChoice)site.Fields.GetFieldByInternalName(departmentName);
  departmentField.Choices.Add("Administration");
  departmentField.Choices.Add("Information Services");
  departmentField.Choices.Add("Facilities");
  departmentField.Choices.Add("Operations");
  departmentField.Choices.Add("Sales");
  departmentField.Choices.Add("Marketing");
  departmentField.Group = "Financial Columns";
  departmentField.Update();

  // Client Name.
  string clientName = site.Fields.Add("Client Name", SPFieldType.Text, false);
  SPFieldText clientField =
    (SPFieldText)site.Fields.GetFieldByInternalName(clientName);
  clientField.Group = "Financial Columns";
  clientField.Update();

  /* CREATE CONTENT TYPES */

  // Get the parent document content type.
  SPContentTypeId documentCTypeId = new SPContentTypeId("0x0101");
  SPContentType documentCType = site.AvailableContentTypes[documentCTypeId];

  // Financial Document content type.
  SPContentType financialDocumentCType =
    new SPContentType(documentCType, site.ContentTypes, "Financial Document");
  financialDocumentCType.Group = "Financial Content Types";
  site.ContentTypes.Add(financialDocumentCType);
  financialDocumentCType = site.ContentTypes[financialDocumentCType.Id];

  // Purchase Order content type.
  SPContentType purchaseOrderCType =
    new SPContentType(financialDocumentCType, site.ContentTypes,
                                  "Purchase Order");
  purchaseOrderCType.Group = "Financial Content Types";

  site.ContentTypes.Add(purchaseOrderCType);
  purchaseOrderCType = site.ContentTypes[purchaseOrderCType.Id];

  // Invoice content type.
  SPContentType invoiceCType =
    new SPContentType(financialDocumentCType, site.ContentTypes, "Invoice");
  invoiceCType.Group = "Financial Content Types";

  site.ContentTypes.Add(invoiceCType);
  invoiceCType = site.ContentTypes[invoiceCType.Id];

  /* ADD DOCUMENT TEMPLATES */

  // Upload templates.
  FileStream purchaseOrderTemplate = new 
    FileStream(System.Environment.GetFolderPath(
    Environment.SpecialFolder.CommonProgramFiles) +
    "\\Microsoft Shared\\web server extensions\\12\\
    TEMPLATE\\Features\\FinancialCTypesAPI\\PurchaseOrder.docx",
    FileMode.Open,FileAccess.Read);
  site.Files.Add("_cts/Purchase Order/PurchaseOrder.docx",
    purchaseOrderTemplate);

  FileStream invoiceTemplate = new 
    FileStream(System.Environment.GetFolderPath(
    Environment.SpecialFolder.CommonProgramFiles) +
    "\\Microsoft Shared\\web server extensions\\12\\
    TEMPLATE\\Features\\FinancialCTypesAPI\\Invoice.docx",
    FileMode.Open, FileAccess.Read);
  site.Files.Add("_cts/Invoice/Invoice.docx", purchaseOrderTemplate);

  // Associate templates with content types.
  purchaseOrderCType.DocumentTemplate = "PurchaseOrder.docx";
  invoiceCType.DocumentTemplate = "Invoice.docx";

  /* ADD COLUMNS TO CONTENT TYPES */

  // Department Name column.
  SPFieldLink departmentLink = new SPFieldLink(departmentField);
  purchaseOrderCType = site.ContentTypes[purchaseOrderCType.Id];
  purchaseOrderCType.FieldLinks.Add(departmentLink);
  purchaseOrderCType.Update();

  // Client Name column.
  SPFieldLink clientLink = new SPFieldLink(clientField);
  invoiceCType = site.ContentTypes[invoiceCType.Id]; 
  invoiceCType.FieldLinks.Add(clientLink);
  invoiceCType.Update();

  // Amount column.
  SPFieldLink amountLink = new SPFieldLink(amountField);
  financialDocumentCType.FieldLinks.Add(amountLink);
  financialDocumentCType.Update(true);
}

Deploying Content Types

You should always deploy content types as Features to support a smooth migration from development to production and to make content types available within sites through Feature activation. If you want the content types to be available throughout the entire portal, deploy them by using a site collection Feature, which installs the content types in the top-level Site Content Type Gallery. This gallery is available to every subsite in the collection.

Deploying content types that are created by using declarative XML is a simple matter of referencing the element manifest file in the Feature.xml file. With the element manifest, you should also include references to the document templates. In our scenario, the element manifest is named CTypes.xml, which contains the XML from Listing 1. The following example shows the Feature.xml file for deploying the content types through declarative XML.

<?xml version="1.0" encoding="utf-8" ?>
<Feature 
  Title="Financial Content Types (XML)"
  Description="XML-based Content types for financial documents" 
  Scope="Site"
  Id="AE790003-91BB-499c-A2C9-CF491EDA8B03"
  Hidden="FALSE" 
  xmlns="https://schemas.microsoft.com/sharepoint/"
  Version="1.0.0.0"
  >
  <ElementManifests>
    <ElementManifest Location="CTypes.xml" />
    <ElementFile Location="PurchaseOrder.docx"/>
    <ElementFile Location="Invoice.docx"/>
  </ElementManifests>
</Feature>

Deploying content types that are created through the SharePoint API requires a similar Feature.xml file; however, the element manifest file is replaced with a reference to the assembly containing the feature receiver. The following example shows the Feature.xml file that deploys the content types with a feature receiver.

<?xml version="1.0" encoding="utf-8" ?>
<Feature 
  Title="Financial Content Types (API)"
  Description="API-created Content types for financial documents" 
  Scope="Site"
  Id="F0F76D40-F1A3-4705-8173-2479245375F3"
  Hidden="FALSE" 
  xmlns="https://schemas.microsoft.com/sharepoint/"
  Version="1.0.0.0"
  ReceiverAssembly="FinancialCTypesAPI, Version=1.0.0.0,
  Culture=neutral,PublicKeyToken=ee7f86cbf38a80ce"
  ReceiverClass="FinancialCTypesAPI.Receiver"
  >
  <ElementManifests>
    <ElementFile Location="PurchaseOrder.docx"/>
    <ElementFile Location="Invoice.docx"/>
  </ElementManifests>
</Feature>

Using Content Types in Custom Lists

Because content types are so closely associated with lists, you will often find that you want to create and deploy a custom list with your custom content types. You can create custom lists from scratch by using CAML, or create them through the SharePoint API in a feature receiver.

When you create a custom list by using CAML, you can specify the content types to use in the list in the schema.xml file of the list. This file specifies the content types and the fields and views associated with the custom list. Typically, you can start with the schema.xml file of an existing list as the basis for your new custom list. In our scenario, the schema.xml file for the standard document library was used as the starting point and modified to make use of the financial content types, as shown in the following example.

<List xmlns:ows="Microsoft SharePoint" Title="Financial Documents"
  Direction="$Resources:Direction;" Url="Financial Documents"
  BaseType="1"
  EnableContentTypes="TRUE">
  <MetaData>
    <ContentTypes>
      <ContentTypeRef ID="0x01010012841A8869DB425cB829C3875EC558CE01">
        <Folder TargetName="Forms/Document" />
      </ContentTypeRef>
      <ContentTypeRef ID="0x01010012841A8869DB425cB829C3875EC558CE02">
        <Folder TargetName="Forms/Document" />
      </ContentTypeRef>
      <ContentTypeRef ID="0x0120" />
    </ContentTypes>

When you create a custom list by using the SharePoint API, you use a feature receiver assembly. Within the feature receiver, you can create a new list based on an existing list type and then modify it to use your new content types. The following code example shows how to create a new document library and then add the custom content types so that it becomes a Financial Documents library.

public override void FeatureActivated
    (SPFeatureReceiverProperties properties)
{
  SPWeb site = (SPWeb)properties.Feature.Parent;

  // Create new library.
  Guid financialLibraryId = site.Lists.Add("Financial Documents",
    "A library of financial documents",
    SPListTemplateType.DocumentLibrary);

  // Configure the library.
  SPList financialLibrary = site.Lists[financialLibraryId];
  financialLibrary.ContentTypesEnabled = true;
  financialLibrary.OnQuickLaunch = true;
  financialLibrary.Update();

  // Add content types.
  SPContentType invoiceCType =
    site.Site.OpenWeb().ContentTypes["Invoice"];
  SPContentType purchaseOrderCType =
    site.Site.OpenWeb().ContentTypes["Purchase Order"];
  financialLibrary.ContentTypes.Add(invoiceCType);
  financialLibrary.ContentTypes.Add(purchaseOrderCType);
  financialLibrary.Update();

  // Remove the default CType.
  foreach (SPContentType ctype in financialLibrary.ContentTypes)
  {
      if(ctype.Name.Equals("Document"))
          ctype.Delete();
  }
}

Updating Content Types

After content types are deployed and in use, you must plan carefully before you alter them. Remember that additional content types might have been created subsequently and might be relying on site columns or document templates defined in the parent. Additionally, lists that use the content types could have altered characteristics of the site columns, and pushing down changes from the parent could overwrite column settings in the associated lists. This means that any changes to the deployed content types could have significant effect on other dependent features.

When updating content types, you can choose from two different techniques. The first technique is to create a new content type that embodies the changes you want and that will be deployed side-by-side with the existing content type. The second technique is to make changes to the existing content type directly.

If you choose to create a new content type and deploy it side-by-side with the existing content type, you typically hide the existing content type so that it can no longer be used. This technique ensures that any dependent content types and lists continue to function while preventing the existing content type from being used in the future.

In our scenario, imagine that you need to update your solution with a Boolean field that indicates whether the CFO must approve the document. The following example shows how two new content types—Purchase Order 2 and Invoice 2—are created that have a new field named Requires CFO Approval. You then use these content types to supersede the existing Purchase Order and Invoice content types.

<?xml version="1.0" encoding="utf-8" ?>
<Elements xmlns="https://schemas.microsoft.com/sharepoint/">
 <Module Name="PurchaseOrder2DocumentTemplate"
         Url="_cts/Purchase Order2" RootWebOnly="TRUE">
  <File Url="PurchaseOrder2.docx" Type="Ghostable" />   
 </Module>
 <Module Name="Invoice2DocumentTemplate"
         Url="_cts/Invoice2" RootWebOnly="TRUE">
  <File Url="Invoice2.docx" Type="Ghostable" />
 </Module>
 <Field
  ID="{FABB1A04-F981-4847-9267-E7E7D4CD61E5}" 
  Name="RequiresCFO" DisplayName="Requires CFO Approval"
  Type="Boolean" Group="Financial Columns">
 </Field>
 <ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE03"
  Name="Purchase Order2" Version="0"
  Group="Financial Document Types">
  <DocumentTemplate TargetName="PurchaseOrder2.docx" />
  <FieldRefs>
   <FieldRef
    ID="{8AE47811-8E98-4f49-AAE8-CF52A4BF83AB}"
    Name="DepartmentName" DisplayName="Department" />
   <FieldRef
    ID="{FABB1A04-F981-4847-9267-E7E7D4CD61E5}"
    Name="RequiresCFO" DisplayName="Requires CFO Approval" />
  </FieldRefs>
 </ContentType>
 <ContentType
  ID="0x01010012841A8869DB425cB829C3875EC558CE04"
  Name="Invoice2" Version="0"
  Group="Financial Document Types">
  <DocumentTemplate TargetName="Invoice2.docx" />
  <FieldRefs>
   <FieldRef
    ID="{E9FC44C2-9036-490d-9D7C-599A9366779F}"
    Name="ClientName" DisplayName="Client Name" />
   <FieldRef
    ID="{FABB1A04-F981-4847-9267-E7E7D4CD61E5}"
    Name="RequiresCFO" DisplayName="Requires CFO Approval" />
  </FieldRefs>
 </ContentType>
</Elements>

After the new content types are created, you should hide the existing content types that will be superseded. You hide the existing content types in a feature receiver. Because code in a feature receiver is always executed after the processing of the declarative XML, you are ensured that the new content types will exist before the code runs. The following code example shows how to hide the existing content types.

SPSite siteCollection = (SPSite)properties.Feature.Parent;
SPWeb site = siteCollection.OpenWeb();

// Get content types.
SPContentType financialDocumentCType =
  site.ContentTypes["Financial Document"];
SPContentType purchaseOrderCType =
  site.ContentTypes["Purchase Order"];
SPContentType invoiceCType = site.ContentTypes["Invoice"];

// Hide old content types.
purchaseOrderCType.Group = "_Hidden";
purchaseOrderCType.Update();
invoiceCType.Group = "_Hidden";
invoiceCType.Update();

If you choose to update the existing content types, you can also make use of a feature receiver. When you update the existing content types, you can push down the changes to all of the dependent child content types and lists. You push down the changes by passing the value true in the Update method of the SPContentType object. Again, carefully consider the effect of pushing changes down because they will overwrite settings in the dependent content types and lists. The following code example shows how to add three new columns to the existing content types and push those changes down.

SPSite siteCollection = (SPSite)properties.Feature.Parent;
SPWeb site = siteCollection.OpenWeb();

/* CREATE NEW COLUMNS */

// Tracking Number.
string trackingNumberName =
  site.Fields.Add("Tracking Number", SPFieldType.Text, false);
SPFieldText trackingNumberField =
  (SPFieldText)site.Fields.GetFieldByInternalName(trackingNumberName);
trackingNumberField.Group = "Financial Columns";
trackingNumberField.Update();

// Order Number.
string orderNumberName = site.Fields.Add("Order Number", SPFieldType.Text, false);
SPFieldText orderNumberField =
  (SPFieldText)site.Fields.GetFieldByInternalName(orderNumberName);
orderNumberField.Group = "Financial Columns";
orderNumberField.Update();

// Invoice Number.
string invoiceNumberName = site.Fields.Add("Invoice Number", SPFieldType.Text, false);
SPFieldText invoiceNumberField =
  (SPFieldText)site.Fields.GetFieldByInternalName(invoiceNumberName);
invoiceNumberField.Group = "Financial Columns";
invoiceNumberField.Update();

/* ADD COLUMNS TO CONTENT TYPES */

// Get content types.
SPContentType financialDocumentCType =
  site.ContentTypes["Financial Document"];
SPContentType purchaseOrderCType =
  site.ContentTypes["Purchase Order"];
SPContentType invoiceCType =
  site.ContentTypes["Invoice"];

// Purchase Order content type.
SPFieldLink orderNumberLink = new SPFieldLink(orderNumberField);
purchaseOrderCType.FieldLinks.Add(orderNumberLink);
purchaseOrderCType.Update(true);

// Invoice content type.
SPFieldLink invoiceNumberLink = new SPFieldLink(invoiceNumberField);
invoiceCType.FieldLinks.Add(invoiceNumberLink);
invoiceCType.Update(true);

// Financial Document content type.
SPFieldLink trackingNumberLink = new SPFieldLink(trackingNumberField);
financialDocumentCType.FieldLinks.Add(trackingNumberLink);
financialDocumentCType.Update(true);

Conclusion

When developing content types for custom solutions, you can choose between a declarative approach and a programmatic approach. Initially, you might find it simpler to create and deploy content types declaratively because no code is required. When updating existing content types, however, you will likely need to write code to handle the updating and make decisions about how to push changes down the content type hierarchy. In all cases, you must carefully consider the effect of content type changes on existing documents and data.

About the Author

Scot Hillier is an independent consultant and Microsoft Most Valuable Professional (MVP) whose focus is creating solutions for information workers with SharePoint, Office, and related .NET Framework technologies. He is the author of 10 books on Microsoft technologies including Microsoft SharePoint: Building Office 2007 Solutions in C# 2005 (Expert's Voice in SharePoint). Scot divides his time between consulting on SharePoint projects and training for the Critical Path Training, LLC. Scot is a former U.S. Navy submarine officer and graduate of the Virginia Military Institute. Scot can be reached at scot@shillier.com.

Additional Resources

For more information, see the following resources: