11.2. 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, and 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 11-2 shows some of the default content types and their identifiers.

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 0x010102, which SharePoint reads as id 02 (Picture) derived from Document (0x0101) derived from Item (0x01) derived from System (0x).

Figure 11.2. Figure 11-2

For custom content types that you define yourself, the identifier includes a suffix, which is the globally unique identifier (GUID) associated with our type, separated by 00 as a delimiter. For example, the project proposal content type defined later in this chapter has the ID

0x0101004A257CD7888D4E8BAEA35AFCDFDEA58C

Again, reading from right to left, you have 4A257CD7888D4E8BAEA35AFCDFDEA58C derived from Document (0x0101) derived from Item (0x01) derived from System (0x). The 00 serves as a delimiter between the GUID and the rest of the identifier, as shown in Figure 11-3.

Figure 11.3. Figure 11-3

Each content type references a set of columns (also called fields), which compose 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 your 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 your ability to add enhanced functionality through code.

The following sections explore both methods. You will use XML to declare the metadata for your custom project proposal content type and then the object model to control the behavior of each instance. As an alternative to using XML for the content type declaration, you will also explore ways to create content types entirely through code.

11.2.1. Declaring Content Types Using XML

To define a content type using XML, the following steps are required:

  1. Create a content type definition file.

  2. Create a field definition file that describes any custom fields.

  3. Create a feature definition file that references the content type definition.

  4. Install the feature into SharePoint.

11.2.1.1. The Content Type Definition File

Listing 11-1 shows the content type definition XML for the project proposal type. The content type ID is specified and indicates that the project proposal inherits the fields of the built-in Document type. The content type name "Project Proposal" is used to reference our type from code and from the SharePoint user interface.

Example 11.1. Project proposal content type definition
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
        <!-- _filecategory="ContentType"
_filetype="Schema" _filename="contenttype.xml" _uniqueid="cff96a1e-6a52-4462-a3d0-d01471b8bfef" -->
        <ContentType ID="0x0101004a257cd7888d4e8baea35afcdfdea58c"
                Name="Project Proposal"
                Group="ProSharePoint2007"
                Description="A Content Type for Managing Project Proposals"
                Version="0">
                <FieldRefs>
                        <FieldRef ID="{246D0907-637C-46b7-9AA0-0BB914DAA832}"Name="Author"/>

<FieldRef ID="{76A81629-44D4-4ce1-8D4D-6D7EBCD885FC}"Name="Subject" />
                        <FieldRef ID="{9DC4BA7E-6C50-4e24-9797-355131089A2E}"Name="ProposalType" Required="TRUE"/>
                        <FieldRef ID="{24A18FDA-927A-4232-88AE-F713FFD3FBB4}"Name="EstimatedCost"/>
                        <FieldRef ID="{41495470-9EA3-46e0-9A34-0E0C3DE1A445}"Name="BidAmount"/>
                        <FieldRef ID="{D46C0900-5617-414c-97E5-E5626DBC1495}"Name="EstimatedHours"/>
                        <FieldRef ID="{79368859-EAF8-4361-8A9D-C3CBD9C88697}"Name="HourlyRate"/>
                        <FieldRef ID="{64cd368d-2f95-4bfc-a1f9-8d4324ecb007}"Name="StartDate" />
                        <FieldRef ID="{8A121252-85A9-443d-8217-A1B57020FADF}"Name="EndDate" />
                        <FieldRef ID="{1DAB9B48-2D1A-47b3-878C-8E84F0D211BA}"Name="Status" />
                        <FieldRef ID="{52578FC3-1F01-4f4d-B016-94CCBCF428CF}"Name="Comments" />
                        <FieldRef ID="{B66E9B50-A28E-469b-B1A0-AF0E45486874}"Name="Keywords" />
                </FieldRefs>
        </ContentType>
</Elements>

Adding fields to a content type requires the use of FieldRef elements. This is because content types do not declare fields directly but instead refer to site columns that are defined globally within the current SharePoint execution context.

You will see shortly how site columns are declared, but for now note that each FieldRef element specifies the unique identifier of a separate Field element defined elsewhere within the solution. This includes both the built-in site columns that are shipped with Windows SharePoint Services, and any custom fields you define.

This presents a bit of a problem when building content type definition files. You already know the identifiers of the custom fields because you created them yourself. But for the built-in site columns, you first have to locate the appropriate identifiers that are recognized by SharePoint. When creating content types through the user interface, SharePoint looks up the field identifiers automatically. A bit of additional work is required when building a solution that installs custom content types at runtime.

The built-in site columns are declared in the fieldswss.xml file, which is located in the 12TEMPLATEFEATURESfields folder. To declare a content type based on built-in site columns, you must search through this file to find the field you want, and then copy the GUID from the file into your content type definition XML.

To simplify the process of looking up field identifiers, use a simple XSL style sheet to display the fieldswss.xml file as an HTML table. The style sheet shown in Listing 11-2 produces the table shown in Figure 11-4.

Example 11.2. XSL style sheet to locate built-In field IDs
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:wss="http://schemas.microsoft.com/sharepoint/">
        <xsl:output method="html" version="1.0" encoding="utf-8" indent="yes"/>
        <xsl:template match="wss:Elements">
                <html>
                        <body>
                                <h2>SharePoint v3 Built-In Fields</h2>
                                <table border="0" width="100%" style="font-size:9pt;">
                                    <tr bgcolor="#9acd32">
                        <th align="left">Group</th>
                        <th align="left" width="100">Field</th>
                        <th align="left">Type</th>
                        <th align="left">Declaration</th>
                    </tr>
                    <xsl:apply-templates>
                        <xsl:sort select="@Group"/>
                        <xsl:sort select="@Name"/>
                    </xsl:apply-templates>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="wss:Field">
        <tr>
            <td width="100"><xsl:value-of select="@Group"/></td>
            <td width="100"><xsl:value-of select="@Name"/></td>
            <td width="100"><xsl:value-of select="@Type"/></td>
            <td>
                &lt;FieldRef ID=&quot;<xsl:value-of select="@ID"/>&quot;
                    Name=&quot;<xsl:value-of select="@Name"/>&quot; /&gt;
            </td>
        </tr>
    </xsl:template>
</xsl:stylesheet>

This table comes in handy when writing content type definition XML files. In this example, you include not only the group, field name, and underlying data type but also a text string you can easily copy and paste into the content type definition file.

11.2.1.2. The Field Definition File

For the five custom fields of the project proposal, you have to create a set of site columns that will be deployed along with the solution. Site columns are defined in XML using CAML Field elements. Each Field element declares the unique identifier, field type, field name, description and other properties.

Figure 11.4. Figure 11-4

Listing 11-3 shows the custom field declarations for the project proposal content type.

Example 11.3. Project proposal custom field declarations
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <Field
        ID="{9DC4BA7E-6C50-4e24-9797-355131089A2E}"
        Description="Select the proposal type from the available choices."
        Type="Choice"
        Name="ProposalType"
        DisplayName="Proposal Type"
        StaticName="_ProposalType"
        >
        <CHOICES>
          <CHOICE>$Resources:ProposalManager,_Choice_FixedBid</CHOICE>
          <CHOICE>$Resources:ProposalManager,_Choice_TimeAndMaterials</CHOICE>

</CHOICES>
    </Field>
    <Field
        ID="{24A18FDA-927A-4232-88AE-F713FFD3FBB4}"
        Description="The estimated cost of performing the work."
        Type="Currency"
        Name="EstimatedCost"
        DisplayName="Estimated Cost"
        StaticName="_EstimatedCost"
        />
    <Field
        ID="{41495470-9EA3-46e0-9A34-0E0C3DE1A445}"
        Description="The total bid amount"
        Type="Currency"
        Name="BidAmount"
        DisplayName="Bid Amount"
        StaticName="_BidAmount"
        />
    <Field
        ID="{D46C0900-5617-414c-97E5-E5626DBC1495}"
        Description="The estimated person-hours for the project."
        Type="Number"
        Name="EstimatedHours"
        DisplayName="Estimated Hours"
        StaticName="_EstimatedHours"
        />
    <Field
        ID="{79368859-EAF8-4361-8A9D-C3CBD9C88697}"
        Description="The negotiated hourly rate."
        Type="Currency"
        Name="HourlyRate"
        DisplayName="Hourly Rate"
        StaticName="_HourlyRate"
        />
</Elements>

A Note about Resources

SharePoint uses a special syntax to enable XML definition files to reference strings and other resources at runtime. This powerful new feature enables developers to create more robust solutions with built-in localization support. Resource tags can also be used to keep the code synchronized with the XML definition files, thereby saving time during development.

The $Resources part of the tag indicates that the string should be retrieved from a RESX file stored in the 12Resources folder. The next part of the tag specifies the name of the RESX file and the resource identifier to retrieve. In this case, the 12Resources ProposalManager.resx file contains a string resource named _Choice_FixedBid that in turn holds the text "Fixed Bid".


11.2.1.3. The Feature Definition File

The final step is to create a feature definition that references the content type so you can activate the feature within the SharePoint environment. Listing 11-4 shows the feature definition file for the Proposal Management application.

Example 11.4. Proposal management feature declaration
<Feature  Title="ProposalManagerFeature"
    Id="63d38c9c-3ada-4e07-873f-a278443e910c"
    Description=""
    Version="1.0.0.0"
    Scope="Web"
    Hidden="TRUE"
    DefaultResourceFile="core"
    ReceiverAssembly="ProposalManager, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=9f4da00116c38ec5"
    ReceiverClass="ProSharePoint2007.ProposalManagerFeature"
    xmlns="http://schemas.microsoft.com/sharepoint/">
</Feature>

11.2.2. Defining 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

11.2.2.1. Automatic Resolution of Built-In Field Identifiers

Setting up field references for content types declared using XML requires that the unique field identifier be 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"]));
    }
}

11.2.2.2. 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 set up 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.

11.2.2.3. 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 that you don't have to start from scratch each time you need a new content type.

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

Example 11.5. 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 preexisting[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 either be instantiated from an XML definition associated with a feature or be created entirely in code. Listing 11-6 shows the declaration for the project proposal type derived from the generic content type wrapper class.

Example 11.6. 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 11-5.

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 set up the document library to hold the proposal documents.

Listing 11-7 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.

Figure 11.5. Figure 11-5

Example 11.7. 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 your custom content types. Listing 11-8 illustrates this process.

Example 11.8. 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 ProposalManagerFeature : SPFeatureReceiver
    {
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override void FeatureActivated(
                    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 your Project Proposal content type. Figure 11-6 shows the home page of a site created from the site definition.

Figure 11.6. Figure 11-6

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.245.137