Lists and Content Types


The central component to collaborative applications is a flexible data model, with which users can store, manage, and collaborate on data. This data may represent people, things, or documents and can store a great amount of metadata about the actual pieces of data. For example, a document may have multiple keywords associated with it as well as approval status, author information, and tracking data. Another example of collaborative data may be represented by a customer who has associated industry-related data as well as people-related data and may link to data in additional lists.

Windows SharePoint Services List Data

Windows SharePoint Services (WSS) implements collaborative data with lists and content types, both of which are defined based on an XML-defined schema that is either created at runtime through user customization or predefined on the file system in XML-based files within features. These XML-based files are written in a WSS-specific language known as Collaborative Application Markup Language (CAML). When you develop WSS type definitions with CAML-such as site columns, content types, and list templates-you are creating provisioning components that users can use in their own collaborative applications. When you create site columns, content types, and list templates through the WSS user interface, you are creating types of customized content that exist within the scope of a single site.

Although provisioning components are harder to create and test, they can be more easily reused across any site in a farm, and they can be packaged and deployed in WSS solution packages for remote deployment. Customized content within a live site, on the other hand, is not as reusable. The design and creation of provisioning components is the preferred approach when you want to reuse, repackage, or resell your components. However, customizing content through the WSS user interface is good for rapid prototyping or continuing to evolve a single site in production.

When developing provisioning components, you will find that it can be difficult, because there is little debugging support and often the error messages you get are cryptic. To be successful at developing provisioning components, you will want to use a variety of development techniques including automated testing and deployment scripts during the development process.

You will also want to refer to and dissect the built-in features and provisioning components that ship with WSS, as well as those that ship with Microsoft Office SharePoint Server 2007 (MOSS). Copying and editing these components will often get you very close to what you want, and adding custom functionality can be more stable than rewriting at times. However, as a rule of thumb, you should never modify the features and provisioning components that are included with the product. Instead, copy their files or their XML content into your own feature and provisioning components and then modify the copy.

Note 

Although some of the C# code samples in this chapter will be written in console applications, you also could program similar code within feature event handlers. Note that the Console application code references http://localhost, because console applications in WSS can run against only the local server.

Working with WSS Lists

At the heart of the core WSS architecture is the infrastructure for defining list types and provisioning list instances to store content. Document libraries, which play a vital role in creating WSS business solutions, can be seen as hybrid lists that leverage and extend the same mechanisms and storage model that are used by standard lists.

WSS ships with a variety of built-in list types (shown in Table 6-1) that can solve many business needs without requiring custom development. These list types are visible on the standard WSS Create page, and they enable users to quickly create list instances on an ad hoc basis. Within the Create page, these built-in list types are broken out into sections including Libraries, Communications, Tracking, and Custom Lists.

Table 6-1: WSS List Definitions
Open table as spreadsheet

List Type

Description

Document library

Used for collaborating on documents with support for versioning, check-in and check-out, and workflow. Includes support for deep integration with Microsoft Office.

Form library

Used to store XML documents and forms for use with Microsoft Office InfoPath.

Wiki page library

Used for collaborative Web pages based on wiki pages, which are dynamically generated and collaboratively edited Web pages.

Picture library

A specialized document library enhanced for use with pictures. Includes support for slide shows, thumbnails, and simple editing through Microsoft Office Picture Manager.

Announcements

Used for simple sharing of timely news with support for expiration.

Contacts

A list for tracking people and contact information, with support for integration into Microsoft Office Outlook and other WSS-compatible contacts applications.

Discussions

A simple list for threaded discussions with support for approval and managing discussion threads.

Links

A list for managing hyperlinks.

Calendar

A list for tracking upcoming events and deadlines. Includes support for integration and synchronization with Office Outlook.

Tasks

A list of activity-based items that can integrate with workflow.

Project tasks

An enhanced tasks list with support for Gannt chart rendering and integration with Microsoft Office Project.

Issue tracking

A list for tracking issues and resolution, with support for prioritization.

Custom list

An empty list definition for extending with custom columns, or created using Microsoft Office Excel spreadsheets.

Tip 

WSS galleries such as Web Part galleries and master page galleries are all implemented based on specialized document libraries.

At a lower level, WSS classifies list types using base types. Standard lists have a base type of 0, whereas document libraries have a base type of 1. There also are less frequently used base types for discussion forums (3), vote or survey lists (4), and issue lists (5). The base type defines a common set of columns, and all list types that are based on that base type automatically inherit those columns. For example, each of the built-in base types defines an ID field. This enables WSS to track each item in a list and to track each document in a document library behind the scenes with a unique integer identifier. WSS also adds several columns to the base type for document libraries that are not needed for standard list types.

List instances can be created either by users through the WSS user interface or by developers through the WSS object model. Later in the chapter, you also will see that you can create a list instance in a declarative fashion by adding a CAML element in a feature. Let’s start with a basic code sample that demonstrates how to create a new list instance from one of the built-in list types.

Listing 6-1 provides the code to create a list instance. Before creating the list, the code checks to make sure a list of the same title doesn’t already exist. You will notice that the code enumerates through the lists within the current site, checking each list to see if there is a matching title. If a list with a matching title does not exist, the code in this application then creates a new instance of the Announcements list type and adds a link to the Quick Launch menu for easy access.

Listing 6-1: Creating a new list instance using the WSS object model

image from book
  List Access Through the WSS Object Model using System; using Microsoft.SharePoint; class Program {   static void Main() {     using (SPSite site = new SPSite("http://localhost")) {       using (SPWeb web = site.OpenWeb()) {         string listName = "Litware News";         SPList list = null;         foreach (SPList currentList in web.Lists) {           if (currentList.Title.Equals(listName,                                        StringComparison.InvariantCultureIgnoreCase)) {             list = currentList;             break;           }         }         if (list == null) {           Guid listID = web.Lists.Add(listName,                                       "List for big news items",                                       SPListTemplateType.Announcements);           list = web.Lists[listID];           list.OnQuickLaunch = true;           list.Update();         }       }     }   } } 
image from book

Note the required call to the Update method on the SPList object at the end of this listing. This is required to save any changes you have made to list properties, such as, in this case, assigning a value of “true” to the OnQuickLaunch property.

Lists can also be accessed by using the GetList methods of the SPWeb class:

 SPList announcementsList = web.GetList("/Lists/Announcements");

The GetList method takes a site-relative path to the list folder or a list form page as an argument. If the list instance is not found, the GetList method will throw an exception of type FileNotFoundException. The only way to check if a list exists without throwing an exception is to enumerate the site object’s lists and check for its existence.

Tip 

GetList is the preferred method to access a list by a URL. GetListFromUrl and GetListFromWebPartPageUrl function the same way as GetList but throw a generic SPException on failure rather than the more descriptive FileNotFoundException.

After you have a reference to an SPList object for the list, you can create a new list item by adding an SPListItem to its Items collection. The SPListItem is a generic item with fields corresponding to the fields in the list. You can create and save a new list item by using the following code:

 SPListItem newItem = list.Items.Add(); newItem ["Title"] = "Litware Goes Public!"; newItem ["Body"] = " We all live in exciting times."; newItem["Expires"] = DateTime.Now + TimeSpan.FromDays(2); newItem.Update();

The Update method of the SPListItem object commits the changes to the list. If you don’t call the Update method, the list item data will not be saved. The fields (columns) of the list are specified using the display name. They can also be accessed by the GUID identifier of the field or the zero-based index in the Fields collection. If a field is specified that is not in the Fields collection for the list, an ArgumentException will be thrown. In some scenarios, you may want to enumerate through the fields in a list by using a foreach construct to ensure the field you are looking for really exists.

 foreach (SPField field in list.Fields) {   if (!field.Hidden && !field.ReadOnlyField)     Console.WriteLine(field.Title); }

Enumerating through the fields also can be useful when enumerating list items. You can use the Fields collection to access data from the list item. To limit the fields displayed, you may want to display only user editable fields as shown in the following code example:

 foreach (SPListItem item in list.Items) {   foreach (SPField field in list.Fields) {     if (!field.Hidden && !field.ReadOnlyField)       Console.WriteLine("{0} = {1}", field.Title, item[field.Id]);   } }

Using Queries for List Data

To get back specific results within a list, you can use the SPQuery object. When you use an SPQuery object, you will create CAML statements to select specific data within the target list. To select announcements that have expired, you may want to use a query built with CAML statements, as shown in the following example:

 SPQuery query = new SPQuery(); query.ViewFields = @"<FieldRef Name='Title'/><FieldRef Name='Expires'/>"; query.Query = @"<Where>    <Lt>      <FieldRef Name='Expires' />      <Value Type='DateTime'>      <Today /></Value>    </Lt> </Where>"; SPList list = site.Lists["Litware News"]; SPListItemCollection items = list.GetItems(query); foreach (SPListItem expiredItem in items) {   Console.WriteLine(expiredItem["Title"]); }

You must specify the fields you want returned in the query by using the ViewFields property. Also note that you must specify the fields in terms of the field Name, and not DisplayName. If you attempt to access fields without specifying them in ViewFields, you will experience an exception of type ArgumentException.

The basic syntax for the query is “<Where><operator><operand /><operand /></operator> </Where>”. Table 6-2, which appears later in this chapter, lists the basic CAML you will use with queries; for a more complete listing see the SDK.

SPQuery is a great way to get back items from a single list. Furthermore, using SPQuery can be significantly faster than enumerating through all the items within a particular list when you are looking only for items that match certain criteria. However, WSS 3.0 introduces a new query mechanism via the SPSiteDataQuery class. A query run with the SPSiteDataQuery class can return items from many different lists through an entire site collection. For this reason, queries run with the SPSiteDataQuery class are sometimes referred to as cross-site queries.

As you saw in the last example, queries run against an SPQuery object return an SPListItemCollection. Queries run with an SPSiteDataQuery object are different, because they return an ADO.NET DataTable object. Just as with SPQuery, columns that are returned in the DataTable are specified as fields. For example, imagine a scenario in which you want to run a single query against every list in the current site collection that has been created from the Announcements list type and return all list items that were created today. The following code sample demonstrates how to do this by creating an SPSiteDataQuery object, initializing it with the necessary CAML statements, and then passing it to the current SPWeb object’s GetSiteData method.

 SPSiteDataQuery query = new SPSiteDataQuery(); query.Lists = @"<Lists ServerTemplate='104' />"; query.ViewFields = @"<FieldRef Name='Title'/><FieldRef Name='Created'/>"; query.Webs = "<Webs Scope='SiteCollection' />"; string queryText = @"<Where>    <Eq>      <FieldRef Name='Created' />      <Value Type=""DateTime"">        <Today />      </Value>    </Eq>  </Where>"; query.Query = queryText; DataTable table = site.GetSiteData(query); foreach (DataRow row in table.Rows) {   Console.WriteLine(row["Title"].ToString()); }

This example assigns a CAML statement to the Lists property that specifies the ServerTemplate of 104, which is the list type identifier for the Announcements list. Even though GetSiteData is a method of the SPWeb reference, the query is performed against all sites in the current site collection. The scope of the query is controlled through a CAML statement in the SPSiteDataQuery’s Webs property, which assigns a value of “SiteCollection” to the Scope attribute. You can limit the query to a scope of “Site” to just query the current site or to a scope of “Recursive” to query the current site and all the child sites beneath it.

If you need to get a reference to the actual list item, you can get it by using the columns WebId, ListId, and ID.

 SPWeb parentWeb = web.Site.OpenWeb(new Guid(row["WebId"].ToString())); SPList list = parentWeb.Lists[ new Guid(row["ListId"].ToString()) ]; SPListItem item = list.GetItemById((int.Parse(row["ID"].ToString())));

The SPSiteDataQuery class is perhaps most useful for creating data-aggregation Web Parts, or data-aggregation XML feeds, such as a recently published RSS feed. Listing 6-2 displays sample code for a recently published RSS feed. This same code could be used within a Web Part to create a rollup Web Part for any type of list, or you could use query parameters to vary the scope of the recently published items.

When using the SPSiteDataQuery class, you should note that only items that match the schema of the ViewFields parameter are returned. You also can filter the results by the ContentType field. In this case, we will filter on the Post content type. To use this handler, register it with the following element within the httpHandlers node of web.config:

 <add verb="GET" path="recent.rss" type= "LitwareHandlers.RecentPostsHandler, LitwareHandlers"/>

Tip 

Because all SharePoint Requests are routed through the .NET Framework, you do not need to register special file extensions with IIS. This lets you use extensions such as .rss without additional configuration.

Listing 6-2: The site data query applied to recently published Items

image from book
  A "Recently Published" Feed using System; using System.Web; using Microsoft.SharePoint; using System.Data; using System.Xml; using Microsoft.SharePoint.Utilities; namespace Litware.ContentWebParts.Handlers {   public class RecentPostsHandler : IHttpHandler {     public bool IsReusable {       get { return true; }     }     public void ProcessRequest(HttpContext context) {       SPWeb web = SPContext.Current.Web;       SPSiteDataQuery query = new SPSiteDataQuery();       query.ViewFields =       @"<FieldRef Name=""Title""/><FieldRef Name=""PostCategory""/>         <FieldRef Name=""PublishedDate""/><FieldRef Name=""Body""/>         <FieldRef Name=""Author""/><FieldRef Name=""Permalink""/>         <FieldRef Name=""ContentType""/>";       string queryText =       @"<Where>           <And>             <Eq>               <FieldRef Name=""ContentType"" />               <Value Type=""Text"">Post</Value>             </Eq>             <Eq>               <FieldRef Name=""PublishedDate"" />               <Value Type=""DateTime""><Today /></Value>             </Eq>           </And>         </Where>";       query.Query = queryText;       query.Webs = @"<Webs Scope='Recursive' />";       DataTable table = web.GetSiteData(query);       context.Response.ContentType = "text/xml";       XmlTextWriter xw = new XmlTextWriter(context.Response.Output);       xw.WriteStartElement("rss");       xw.WriteAttributeString("version", "2.0");       xw.WriteStartElement("channel");       xw.WriteElementString("title", "Recently Published: " + web.Title);       xw.WriteElementString("description",         "Recently published posts from " + web.Url);       xw.WriteElementString("link", web.Url);       foreach(DataRow row in table.Rows){         xw.WriteStartElement("item");         xw.WriteElementString("title", (string)row["Title"]);         xw.WriteElementString("description", ((string)row["Body"]));         xw.WriteElementString("pubDate", row["PublishedDate"].ToString("r"));         string author =         row["Author"].ToString().Split(new string[] { ";#" },                                        StringSplitOptions.None)[1];         xw.WriteElementString("author", author);         string category =         row["PostCategory"].ToString().Split(new string[] { ";#" },                                              StringSplitOptions.None)[1];         xw.WriteElementString("category", category);         string link = string.Format(@"/Lists/Posts/Post.aspx?ID={0}",                                     row["Permalink"].ToString());         xw.WriteElementString("link", link);         xw.WriteEndElement(); //item       }       xw.WriteEndElement(); //channel       xw.WriteEndElement(); //rss     }   } } 
image from book

Warning 

In WSS 3.0, when working with posts from the blog site, the post “ContentType” is null. However, the ContentType field is set to “Post.”

Table 6-2: Basic CAML Query Elements
Open table as spreadsheet

Element

Description

And

Groups multiple conditions

BeginsWith

Searches for a string at the beginning of the text field

Contains

Searches for a string within the text field

Eq

Equal to

FieldRef

A reference to a field (useful for GroupBy elements)

Geq

Greater than or equal to

GroupBy

Groups results by these fields

Gt

Greater than

IsNotNull

Is not null (not empty)

IsNull

Is null (empty)

Leq

Less than or equal to

Lt

Less than

Neq

Not equal to

Now

The current date and time

Or

Boolean or operator

OrderBy

Orders the results of the query

Today

Today’s date

TodayIso

Today’s date in ISO format

Where

Used to specify the “Where” clause of the query

Tip 

For more information on the SPQuery syntax and a complete listing of CAML elements, consult the WSS SDK’s “Query Syntax” topic under General Reference: Reference: Collaborative Application Markup Language: Query Schema.

Creating Custom List Elements

To create a list using the user interface, use the Site Actions menu to navigate to Create, Custom Lists, Custom List. The form on the resulting New page enables creation of an empty list by using the Custom List Type, which has no user editable columns (fields) except Title. After you create the list, you can then choose Create Column under the Settings menu to add new columns using standard WSS field types. A field type is a data type in WSS that can be used for content. It is similar to a SQL data type (and is defined with a SQL Data Type), although you can create your own field types as well. Table 6-3 displays the default WSS field (column) types available from the Create Column page.

Table 6-3: Basic WSS Built-in Field Types
Open table as spreadsheet

Field Type

Description

Single Line Of Text

A single line of text. The maximum number of characters may be customized.

Multiple Lines Of Text

A text box with plain text, rich text (boldface, italics, text alignment), or enhanced rich text (pictures, tables, links). Rich text boxes are rendered in edit mode with a rich text toolbar.

Choice

A choice of several items you define.

Number

A numerical value. Options include minimum, maximum, decimal places, and so on.

Currency

A monetary value in a specific currency format.

Date And Time

Date information, or date and time information.

Lookup

A field that references a field value in another list on the site.

Yes/No

A Boolean field that is either true or false. Renders as a check box in the user interface.

Person Or Group

A user or member of the current site. This field can render with presence information, which ties into either Windows Live Messenger or Microsoft Office Live Communications Server.

Hyperlink Or Picture

A URL-formatted string.

Calculated

A value that is calculated from other fields.

Defining Custom List Elements with CAML

Now we are going to step through the process of defining site columns, content types, and list schemas. If you want to follow along with our sample code, you can open the Visual Studio project named LitwareTypes. This project contains several different examples of custom provisioning components. If you open and build this project, the install.bat file will copy all the required provisioning files into the TEMPLATES directory and will install a feature named LitwareTypes. Then you should be able to activate the LitwareTypes feature within the scope of any site collection in the farm so that you can begin to work with these sample provisioning components.

WSS provisioning components are created by using CAML to define data schemas and by rendering HTML. The structure of most CAML elements used in WSS is defined in the XML schema files in the TEMPLATE\XML directory. You can enable IntelliSense in Visual Studio 2005 using these schemas by copying these XSD files into %ProgramFiles%\Microsoft Visual Studio 8\Xml\Schemas. Note that CAML definitions can be tricky to program, and there is little debugging support, so testing is essential. For more advice on CAML Programming, see the sidebar “CAML Debugging Through Diagnostic Logging.”

image from book
CAML Debugging Through Diagnostic Logging

CAML content definitions have no debugging support through Visual Studio. This can make it frustrating to develop custom content types, lists, and other provisioning components. You can, however, enable verbose logging through SharePoint Central Administration. From the Operations tab, under Logging and Reporting, select Diagnostic Logging. You can then set Event Throttling to report on all categories, to report on events of information criticality and above, and to report verbosely. For a personal development server, you can use just one log file for a period of ten minutes. The default path for the logs is C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\LOGS. You may want to import the log into Microsoft Office Excel 2007 or just use Visual Studio or Notepad to read the file.

Although the Web interface may only state a vague error message, such as “Exception from HRESULT: 0x81070201,” the diagnostic log will give you the details needed to fix the XML, such as “Failed to retrieve the list schema for feature FBDECD96-62DC-48c88F0A-7B827A042FD9, list template 10001; expected to find it at: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\Template\Features\LitwareTypes\VendorList.”

image from book

Tip 

Visual Studio 2005 extensions for Windows SharePoint Services 3.0 is a powerful toolkit for generating lightweight provisioning components. You may want to work with the extensions’ output as a starting point for your handcrafted provisioning components, although they do not support some of the powerful new features introduced in WSS 3.0.

Defining Site Column Definitions

Lists store their data in columns (also referred to as fields), which can be defined in the context of a list. WSS 3.0 also introduces site columns, which make it possible to define a field in a reusable manner. Rather than defining a field such as FirstName multiple times, WSS defines a site column once and for all in a built-in WSS feature named fields. Listing 6-3 shows how the FirstName site column is defined in CAML using a Field element.

Listing 6-3: The standard FirstName site column definition

image from book
  <Field      Name="FirstName"     Source     StaticName="FirstName"     Group="$Resources:Person_Event_Columns"     DisplayName="$Resources:core,First_Name;"     Type="Text"> </Field> 
image from book

Tip 

The built-in columns are defined in the built-in “fields” feature, which can be a good reference when you are creating your own column definitions.

In Listing 6-3, you can observe that text-based aspects of the FirstName site column are defined using a special syntax with the “$” character, as in $Resources:core,First_Name. This syntax was introduced in ASP.NET 2.0 as a way to pull a localized string out of a resource file. The ability to localize such aspects of a site-column definition makes it possible for WSS to support localization of provisioning components for several different spoken languages. We will cover the topics of localization and globalization later in this book in Chapter 9.

As a simple example, in this chapter we will create a vendor list with the following columns: Company, Contact, Phone, Industry, Company Size, and Activity Notes. We will be able to use predefined WSS site columns for Company, Contact, and Phone. However, we will create two new site column definitions for the Industry column and the Activity Notes column. Later in the chapter, we also will create a custom field type for the Company Size column.

The LitwareTypes project contains a custom feature named LitwareTypes. As you can see from its Feature.xml file, shown in Listing 6-4, this feature is defined with a Scope attribute value of Site, which means it has been designed to be activated at the level of the site collection.

Listing 6-4 : The LitwareTypes feature is scoped at the site collection level.

image from book
  <?xml version="1.0" encoding="utf-8"?> <Feature   Id=""   Title="Chapter 5: Litware Types"   Description="Demo from Inside Windows SharePoint Services (Pattison/Larson)"   Version="1.0.0.0"   Hidden="false"   Scope="Site"   xmlns="http://schemas.microsoft.com/sharepoint/">   <ElementManifests>     <ElementManifest Location="LitwareSiteColumns.xml" />     <ElementManifest Location="LitwareCustomFieldSiteColumns.xml" />     <ElementManifest Location="LitwareContentTypes.xml" />     <ElementManifest Location="VendorList.xml" />   </ElementManifests> </Feature> 
image from book

The first Element Manifest that is referenced in the LitwareTypes feature is LitwareSiteColumns.xml. Inside this CAML-based XML file, you will find the definitions for two site columns named Industry and ActivityNotes, as shown in Listing 6-5. As you can see, each of these site column definitions is defined using a Field element.

Listing 6-5: An example of creating custom column definitions

image from book
  Custom Column Definitions <?xml version="1.0" encoding="utf-8"?> <!-- Litware Column Types --> <Elements xmlns="http://schemas.microsoft.com/sharepoint/">   <Field              Name="Industry"       DisplayName="Industry"       Type="Choice"       Format="RadioButtons"       Group="Litware Columns">     <CHOICES>       <CHOICE>High Tech</CHOICE>       <CHOICE>Legal</CHOICE>       <CHOICE>Medical</CHOICE>     </CHOICES>     <Default>High Tech</Default>   </Field>   <Field              Type="Note"       RichText="TRUE"       AppendOnly="TRUE"       Name="ActivityNotes"       DisplayName="Activity Notes"       Sortable="FALSE"       Source       Group="Litware Columns">   </Field> </Elements> 
image from book

The Industry site column definition is based on the underlying Choice field type, and it provides the user with three different industries from which to pick when assigning its value. The Industry site column definition also specifies that it should be rendered using RadioButtons, using the Format attribute.

The ActivityNotes site column definition is based on the underlying Notes field type, which enables the user to type in large text values that can span multiple lines. Also note that this site column definition has been defined to support rich text as well as the append-only behavior that was introduced in WSS 3.0. Note that any list that contains an append-only site column must have versioning enabled for the behavior to work as intended.

Now that you have seen how to create a site column definition, let’s examine how they appear within a WSS site. Each site has a Site Column Gallery. After the LitwareTypes feature has been within a particular site collection, our custom site column definitions will appear in the Site Columns Gallery for the top-level site. You can see what’s inside the Site Columns Gallery for a site by using the built-in application page named mngfield.aspx, which is accessible through a link in the Site Settings page. An example of inspecting the site columns that have been added by the LitwareTypes feature with this standard application page is shown in Figure 6-1.

image from book
Figure 6-1: Each site contains a Site Column Gallery.

Note that after a site column has been added to the Site Column Gallery, it is available for use in the current site as well as all the child sites below that site in the site hierarchy. Therefore, adding a site column to the Site Column Gallery of a top-level site makes that column available for use in any list throughout the site collection. This provides a motivation for adding site column definitions mainly to features that are scoped at the site collection level as opposed to the site level.

After site column definitions have been added to a site, users can immediately use them when adding columns to a list. For example, a user can click the Add From Existing Site Columns link from the Settings page for a specific list (reached through Settings, List Settings). That makes it possible to add new columns from the available set of site column definitions, as shown in Figure 6-2.

image from book
Figure 6-2: A user can add columns to a list based on visible custom site column definitions.

If you create a custom list and add two columns based on the site column definitions for Industry and ActivityNotes, you can test their behavior. For example, after you have added the Industry and ActivityNotes columns to a list, you should create a new item to see how these custom site columns appear to a typical user. As you can see from Figure 6-3, the Industry column renders as a set of radio buttons that enable the user to select one of its three possible values. The ActivityNotes column enables the user to type in notes or comments. When testing the ActivityNotes column, make sure versioning is enabled on the list itself. You should be able to observe that the user can add new notes without erasing the previous entries.

image from book
Figure 6-3: Custom site column definitions can be added as columns in lists.

Defining Custom Field Types

In the previous section, you saw examples of how to create custom site column definitions based on some of the built-in WSS field types such as Text, Choice, and Notes. However, WSS 3.0 introduces the ability to work at a lower level, where you can actually define your own custom field type. The motivation for doing this is to gain a greater level of control over the initialization, rendering, and data validation that goes on behind a column.

For example, imagine a scenario in which you need to create a column for a custom list that presents the user with a drop-down list that is populated with data from a backend database server or a Web service. Think about another scenario in which you need a column with values that must be constrained using domain-specific validation logic that you would like to write in a managed language such as C# or Visual Basic .NET. These are examples of scenarios in which it makes sense to create a custom field type.

A custom field type represents a new data type for columns. Custom field types are appealing to .NET developers because they are written in managed code and compiled into .NET assembly DLLs that must be installed in the Global Assembly Cache (GAC). Along with managed code to perform initialization and validation, a custom field type is also defined in terms of one or more ASP.NET server-side controls that give you extra control over rendering and enable you to leverage techniques that are popular in standard ASP.NET development.

In order to create a custom field type, you should create a new Class Library project in Visual Studio. The sample code for this chapter already contains a sample project named LitwareFieldTypes that provides all the pieces for implementing and deploying two custom field types. Note that the LitwareFieldTypes project is configured to compile its output Assembly DLL with a strong name so that it can be installed in the GAC, which is a requirement for deploying and testing a custom field type.

Note 

Like many of the other Visual Studio projects shown in this book, the LitwareFieldTypes project includes an install.bat file that can be run simply by building the project. The batch file installs LitwareFieldTypes.dll in the GAC and copies a few important files into the TEMPLATES directory. After you build the project, you should be able to use its custom field types inside a WSS site.

Inside the source file named LitwareFieldTypes.cs there are two managed classes that are used to define a custom field type named CompanySize. The first class named CompanySizeField is used to define the custom field type itself. This class inherits from the SPFieldText class that is defined inside the core WSS assembly Microsoft.SharePoint.dll. The second class in this example is named CompanySizeFieldControl. This class is used to create and initialize an ASP.NET DropDownList control, which will give our custom field type a custom rendering behavior. Note that the CompanySizeFieldControl class inherits from another class defined inside Microsoft.SharePoint.dll named BaseFieldControl.

Tip 

For a full reference of custom field type base classes, refer to the SDK’s “Custom Field Type Classes” topic. The Field Types all begin with SPField, and include Boolean, choice, currency, date, text, URL, and multiple column fields.

Listing 6-6 shows the code inside the CompanySizeField class. This class provides two standard constructors that are used on all custom field types. There is also a public read-only property named FieldRenderingControl that is used to create an instance of the CompanySizeFieldControl class. Lastly, there is an overridden implementation of a method named GetValidatedString. You override this method to add you own custom validation logic. In our example, we are simply checking to ensure the value is not left empty whenever the column is configured as required. However, you can add additional validation logic as complex as required to solve a particular business requirement.

Listing 6-6: A custom field type is created by creating a class that inherits from SPFieldText or one of the other built-in field type classes.

image from book
  The CompanySizeField Class public class CompanySizeField : SPFieldText {   // each field type requires two standard constructors   public CompanySizeField(SPFieldCollection fields, string fieldName)     : base(fields, fieldName) { }   public CompanySizeField(SPFieldCollection fields, string typeName,                           string displayName)     : base(fields, typeName, displayName) { }   // public property used to instantiate control used for rendering   public override BaseFieldControl FieldRenderingControl {     get {       BaseFieldControl control = new CompanySizeFieldControl();       control.FieldName = this.InternalName;       return control;     }   }   // Standard method override used to add validation logic   public override string GetValidatedString(object value) {     if (this.Required || value.ToString().Equals(string.Empty)) {       throw new SPFieldValidationException("Company size not assigned");     }     return base.GetValidatedString(value);   } } 
image from book

Next, let’s examine how the CompanySizeFieldControl class is written to work together with an ASP.NET User Control file named CompanySizeFieldControl.ascx. In particular, the CompanySizeFieldControl class uses the named RenderingTemplate control defined inside CompanySizeFieldControl.ascx. You should also note that the install.bat file in the LitwareFieldTypes project has been written to copy the CompanySizeFieldControl.ascx file where it needs to be deployed in the TEMPLATE\CONTROLTEMPLATES directory. As you can see, the RenderingTemplate control defined inside CompanySizeFieldControl.ascx is not very complicated.

 <SharePoint:RenderingTemplate  runat="server">   <Template>     <asp:DropDownList  runat="server" />   </Template> </SharePoint:RenderingTemplate>

Now examine the implementation of the CompanySizeFieldControl in Listing 6-7. This class contains a field named CompanySizeSelector that is based on the ASP.NET control type named DropDownList. There is also code inside the CreateChildControls method that binds this field to the instance of the DropDownList control defined in CompanySizeFieldControl.ascx. This makes it possible for code inside this class to initialize the DropDownList control with a set of items.

Listing 6-7: Code to initialize the field control for the CompanySize field type

image from book
  The CompanySizeFieldControl Class public class CompanySizeFieldControl : BaseFieldControl {   protected DropDownList CompanySizeSelector;   protected override string DefaultTemplateName {     get { return "CompanySizeFieldControl"; }   }   public override object Value {     get {       EnsureChildControls();       return this.CompanySizeSelector.SelectedValue;     }     set {       EnsureChildControls();       this.CompanySizeSelector.SelectedValue = (string)this.ItemFieldValue;     }   }   protected override void CreateChildControls() {     if (this.Field == null || this.ControlMode == SPControlMode.Display)       return;     base.CreateChildControls();     this.CompanySizeSelector =           (DropDownList)TemplateContainer.FindControl("CompanySizeSelector");     if (this.CompanySizeSelector == null)       throw new ConfigurationErrorsException("Error: cannot load .ASCX file!");     if (!this.Page.IsPostBack) {       this.CompanySizeSelector.Items.AddRange(new ListItem[]         { new ListItem(string.Empty, null),           new ListItem("Mom and Pop Shop (1-20)", "1-20"),           new ListItem("Small Business (21-100)", "21-100"),           new ListItem("Medium-sized Business (101-1000)", "101-1000"),           new ListItem("Big Business (1001-20,000)", "1001-20000"),           new ListItem("Enterprise Business (over 20,000)", "20000+")});         }   } } 
image from book

The CompanySizeFieldControl class overrides a read-only property named DefaultTemplateName, which returns the string name of RenderingTemplate defined inside CompanySizeFieldControl.ascx. There is code in the base class of the CompanySizeFieldControl class that uses this string to load the RenderingTemplate from CompanySizeFieldControl.ascx at runtime.

You also should examine the overridden implementation of the Value property in the CompanySizeFieldControl class. The inner get method returns the value from the DropDownList control. The inner set method takes the ItemFieldValue property defined by the base class and assigns this value to the DropDownList control.

Now you have seen how two classes and a RenderingTemplate inside an .ascx file work together to provide the implementation of a custom field type. The last piece of the puzzle is getting WSS to recognize that you have introduced a new custom field type into a farm by adding a Field Schema in a CAML-based file that is then copied into a well-known location.

Field types are defined in files named fldtypes*.xml that must be deployed in the TEMPLATE \XML directory. In our case, the field schema for our custom field type named CompanySize is defined using CAML in a file named fldtypes_Litware.xml. The CAML used to define the field schema is shown in Listing 6-8. Also note that the install.bat file for the LitwareFieldTypes project automatically copies this file to the TEMPLATE\XML directory when you compile the project along with installing the assembly file named LitwareFieldTypes.dll into the GAC. After you compile the project, you should then be able to use the CompanySize custom field type.

Listing 6-8: Implementing a custom field type requires writing a field schema using CAML.

image from book
  A Custom Field Type Definition File <?xml version="1.0" encoding="utf-8" ?> <FieldTypes>   <FieldType>     <Field Name="TypeName">CompanySize</Field>     <Field Name="ParentType">Text</Field>     <Field Name="TypeDisplayName">Company Size</Field>     <Field Name="TypeShortDescription">Company Size</Field>     <Field Name="UserCreatable">TRUE</Field>     <Field Name="ShowInListCreate">TRUE</Field>     <Field Name="ShowInSurveyCreate">TRUE</Field>     <Field Name="ShowInDocumentLibraryCreate">TRUE</Field>     <Field Name="ShowInColumnTemplateCreate">TRUE</Field>     <Field Name="FieldTypeClass">       LitwareFieldTypes.CompanySizeField, LitwareFieldTypes, ...     </Field>     <RenderPattern Name="DisplayPattern">       <Switch>         <Expr><Column/></Expr>         <Case Value=""></Case>         <Default>           <HTML><![CDATA[<span style="color:Red"><b>]]></HTML>           <Column SubColumnNumber="0" HTMLEncode="TRUE"/>           <HTML><![CDATA[</b></span>]]></HTML>         </Default>       </Switch>     </RenderPattern>   </FieldType> </FieldTypes> 
image from book

After the custom field type is properly deployed within the farm, you can use it to add a new column to a list and to create a new site column through the browser-based interface of WSS. Figure 6-4 shows how the new custom field type CompanySize appears within the standard page that WSS presents to users for adding a new column to a list.

image from book
Figure 6-4: A custom field type can be used to add a new column to a list.

After you have created a new column within a list based on the CompanySize custom field type, you can test its appearance and behavior. Figure 6-5 shows an example assigning a value to column based on the CompanySize field type. Note that this custom field type renders a DropDownList control that presents the user with a predetermined set of values. Although this simple example has hard-coded the items into the DropDownList control, you can imagine it wouldn’t be that difficult to extend this code to obtain item values for the DropDownList control from an external data source.

image from book
Figure 6-5: The CompanySize field type in use in a custom list.

Defining a Site Column for a Custom Field Type

Earlier in this chapter, we discussed creating custom site columns by using built-in field types such as Text, Notes, and Choice. Now that we have defined a custom field type, we would like to revisit that topic and create another site column by using CAML. However, the site column we will create here will be based on our custom CompanySize field type instead of one of the standard WSS field types.

Listing 6-9 shows the CAML for the Field element required to create a custom site column based on the CompanySize field type. The main point you should observe is that the Type attribute in the Field element is configured with the string name for the custom field type CompanySize. You should recall that the type name CompanySize was defined at the top of fldtypes_Litware.xml, which was shown in Listing 6-8. You can see that after you deploy a custom field type within a farm, its type name can be referenced in places where you would reference the standard WSS field type names, such as in this scenario in which we are creating a custom site column.

Listing 6-9: A site column feature manifest for a custom field type

image from book
  Field in Site Columns Feature Definition <Elements xmlns="http://schemas.microsoft.com/sharepoint/">   <Field          Source     Name="CompanySize"     StaticName="CompanySize"     DisplayName="Company Size"     Type="CompanySize"     Group="Litware Columns">   </Field> </Elements> 
image from book

Defining Items with Content Types

Content types are a powerful new enhancement introduced in WSS 3.0. The central idea is that a content type defines the underlying schema for either an item in a list or a document in a document library. However, it’s important to understand that content types are defined independently outside the scope of any list or document library. After you have created a content type, you can use it across several different lists or several different document libraries.

For example, imagine you create a content type named Company that defines a set of columns for tracking information about a company. After creating this content type you could then create two different lists named Vendors and Customers and configure both of these lists to use the Company content type. This gives you something akin to polymorphism, because you have two different list types that contain homogeneous items that are defined by the same schema. With an application design such as this, you can write a Web Part by using the SPSiteDataQuery that queries across lists and sites to find and display all the items that have content based on the Company content type.

Content types also provide you with the ability to maintain heterogeneous content inside a single list or document library. For example, you can configure a single list to support multiple content types. Imagine a business scenario in which you need to track customers, and those customers may be either companies or individuals. The problem you face is that customers that are companies and customers who are individuals require different columns to track their information. The solution is to create two different content types for each type of customer and then to create a Customers list and configure it to support both content types.

Content types are defined based upon the principles of inheritance. You will never create a content type from scratch. Instead, you always select an existing content type to serve as the base content type for the new content type you are creating. For example, you can create the most straightforward content type by inheriting from the built-in WSS content type named Item. This automatically provides your new content type with the standard columns and behavior that are common across all content types. Alternatively, you can elect to inherit from one of the more complex built-in content types that inherit from the Item content type, such as Announcement, Task, or Document.

Note that when you create a content type, you must decide whether you want it to be used inside document libraries. Only the Document content type and those content types that inherit from Document can be used inside of document libraries. However, these same content types that can be used in document libraries cannot be used in standard lists. For this reason, you can categorize content types as either item-based or document-based. Item-based content types are used exclusively in lists, and document-based content types are used exclusively in document libraries. You should observe that no content type can be used in a list and also in a document library.

In addition to supporting metadata columns, document-based content types also support document templates. For example, you could create a document-based content type named Litware Proposal for tracking a certain type of Word document used in a particular business scenario. When you create such a content type, you can define it with whatever metadata columns are required by the scenario. Furthermore, you can define this content type to use a preformatted document template created with Microsoft Office Word. After creating this document-based content type, you can then configure a document library to support it in such a way that the document library’s New menu gives users the option to create new documents from the document template.

So far, we have discussed how creating a content type allows you to define the state for an item or a document by adding columns. However, it’s important to understand that content types enable you to define the behavior as well. Content types can be used to bind event receivers and workflow associations to items and documents. For example, you can configure the Company content type with an event handler that executes code to perform column value validation. You can configure the Litware Proposal content type with a workflow association that initiates a managerial approval process that must be completed before a proposal document can be sent to a client. Event handlers will be discussed in depth later in this chapter, and a discussion of workflow associations will be deferred until Chapter 8.

Tip 

WSS includes several default content types that are defined in the “ctypes” Feature. These content types can be used as a reference when creating custom content types.

Creating a Content Type Definition in CAML

The LitwareTypes feature that accompanies this chapter defines two different content types in the file LitwareContentTypes.xml. The first content type is named Company, and the second one is named Individual. Listing 6-10 shows the CAML-based definition of the Company content type. As you examine this listing, you can see there is a ContentType element with attributes to define an ID and a Name. There is also an inner FieldRefs element used to define the columns. For each column you want to add, you must create a FieldRef element that references a site column by using its identifying GUID and its Name. You can optionally add a DisplayName attribute that is different than the Name.

Listing 6-10: The Company content type defines an ID and a Name along with a collection of site columns.

image from book
  The Company Content Type Definition <?xml version="1.0" encoding="utf-8" ?> <Elements xmlns="http://schemas.microsoft.com/sharepoint/">   <ContentType          Name="Company"     Description="Create a new company"     Version="0"     Group="Litware Content Types" >     <FieldRefs>       <!-- add in and rename built-in WSS Title column-->       <FieldRef                  Name="Title" DisplayName="Company" Sealed="TRUE" />       <FieldRef                  Name="FullName" DisplayName="Contact" />       <FieldRef                  Name="WorkPhone" DisplayName="Phone" />       <FieldRef                  Name="Industry" />       <FieldRef                  Name="CompanySize" DisplayName="Company Size" />       <FieldRef                  Name="ActivityNotes" DisplayName="Activity Notes" />     </FieldRefs>   </ContentType> </Elements> 
image from book

Unlike a list, it is not possible to create a column in a content type directly based on an underlying field type. Columns within a content type must be defined in terms of existing site columns. Some of the columns in the Company content type shown in Listing 6-10 are based on site columns that are part of WSS, such as Title, FullName, and WorkPhone. Three other columns are based on the custom site columns named Industry, CompanySize, and ActivityNotes that we created earlier this chapter.

As you begin creating content types and lists, it is important to understand that the built-in site column named Title has special significance in WSS. That’s because whatever column is based on the Title site column presents users with the drop-down ECB menu in the AllItems.aspx list view page. Although you should typically use the Title site column, you can change its caption by assigning a value to the DisplayName attribute. In the case of the Company content type, the Title site column has been used to define the first column, and it has been renamed Company. Keep in mind that this column will be used to present the ECB menu.

Tip 

There is no error handling or debugging when deploying features. At best, you may get a cryptic error message such as “Value does not fall within the expected range.” When developing features, it is important to test often and use source code management such as Visual Source Safe so that you can roll back breaking changes.

As you examine the Company content type definition in Listing 6-10, you will notice that the ID attribute used to identify the content type has a long and somewhat complicated format. This requires a little extra attention on your part because the IDs for content types have a very specific naming convention that must be followed. If you fail to properly format the ID for a new content type, you will experience an error when you attempt to activate the feature in which it is defined.

The first part of a content type ID is based on a hexadecimal number that identifies its base content type. The hexadecimal number for the base content type is followed by 00. The last part of a content type ID is a GUID that uniquely identifies the content type.

The hexadecimal number that identifies the System content type is 0x. The hexadecimal number that identifies the root Item Content Type ID is 0x01. To create a content type such as Company that inherits from the Item content type, append a GUID to 0x0100 as follows:

 0x0100E71A2716C18B4e96A9B0461156806FFA

To create a content type that inherits from the Document content type, append a GUID to 0x010100 as follows:

 0x010100A776D19644C04553982B8F1A503E2AA5

Note that content type IDs must be unique within a Site Collection. This should not be a problem as long as you generate a new GUID each time you create a content type. For more information, see the sidebar “Content Type IDs.”

Tip 

To quickly reference the built-in Base Content Types and their IDs, browse to http://localhost/_layouts/mngctype.aspx, the Site Content Type Gallery, and mouse over the links of the Content Types.

image from book
Content Type IDs

Content IDs have a unique naming convention that must be unique within the site collection, and should be unique globally to avoid any conflicts in deployment. The ID is a string formatted with the complete genealogy of its inheritance chain.

Content Type Inheritance Table

Open table as spreadsheet

Content Type

Hexadecimal ID

System

0x

Item

0x01

Document Content Types

Document

0x0101

Form (XMLDocument, or InfoPath form)

0x010101

Picture

0x010102

WikiDocument

0x010108

BasicPage

0x010109

WebPartPage

0x01010901

List Content Types

Event

0x0102

Issue

0x0103

Announcement

0x0104

Link

0x0105

Contact

0x0106

Message

0x0107

Task

0x0108

BlogPost

0x0110

BlogComment

0x0111

Folder Content Types

Folder

0x0120

RootOfList

0x012001

Discussion

0x012002

image from book

Content Types in the Object Model

After activating a feature such as LitwareTypes in which you have defined content types, these content types will then be available for use through the browser-based user interface as well as through the WSS object model. Note that each site has its own Content Type Gallery. The Content Type Gallery for a site can be viewed and administered through an application page named mngctype.aspx that is accessible through a link in the Site Settings page.

Because each site has a Content Type Gallery, you could say that content types are scoped at the site level. However, when you add a content type to the Content Type Gallery within a particular site, that content type is not only available for use within that site but also in all the child sites below it in the site hierarchy. If you add a content type to the Content Type Gallery of a top-level site, it is available for use through the entire site collection. Because the LitwareTypes feature has been designed to be activated within the scope of a site collection, its content types are added to the top-level site and are available for use throughout the site collection.

When programming against content types with the WSS object model, you can determine which content types are available within the current scope by acquiring the SPWeb object for the current site and accessing its AvailableContentTypes property. The AvailableContentTypes property returns a collection including the content types in the Content Type Gallery of the current site as well as the content types of all parent sites. Following is an example using a simple foreach loop to enumerate through and inspect all the available content types for a particular site.

 SPSite siteCollection = new SPSite("http://localhost"); SPWeb site = siteCollection.OpenWeb(); foreach (SPContentType contentType in site.AvailableContentTypes) { Console.WriteLine(contentType.Name); }

If you want to access a particular content type through the WSS object model, you can use its string-based ID to create a ContentTypeId object. The ContentTypeId object then can be used as an index to the AvailableContentTypes property to obtain the SPContentType object you want, as shown in the following code:

 SPWeb site = SPContext.Current.Web; string id = @"0x0100E71A2716C18B4e96A9B0461156806FFA"; SPContentTypeId CompanyTypeID = new SPContentTypeId(id); SPContentType CompanyType = site.AvailableContentTypes[CompanyTypeID];

One of the programmatic tasks you can perform using a content type is to add it to a list. For example, imagine a situation in which you’d like to automate the creation of a new list that supports the Company content type. You can start by creating a new list using the GenericList template and configuring the list with the property settings you require. In the case of a list in which you are adding content types, you probably will want to set the value of the ContentTypesEnabled property to true. After you have created the list and obtained a reference to an SPList object, you can add support for the new content type by calling the Add method on the SPList object’s ContentTypes collection and passing an SPContentType object, as shown in the following code:

  SPSite siteCollection = new SPSite("http://litwareinc.com"); SPWeb site = siteCollection.OpenWeb(); Guid listID = site.Lists.Add("Vendor List",                              "A demo list created through code",                               SPListTemplateType.GenericList); // configure properties for new list SPList list = site.Lists[listID]; list.ContentTypesEnabled = true; list.OnQuickLaunch = true; list.EnableAttachments = false; list.EnableVersioning = true; list.Update(); // add support for Company content type string id = @"0x0100E71A2716C18B4e96A9B0461156806FFA"; SPContentTypeId CompanyTypeID = new SPContentTypeId(id); SPContentType CompanyType = site.AvailableContentTypes[CompanyTypeID]; list.ContentTypes.Add(CompanyType); list.Update(); // remove standard Item content type foreach (SPContentType ContentType in list.ContentTypes) {   if (ContentType.Name.Equals("Item")) {     ContentType.Delete();     break;   } } 

Note that this code not only adds the Company content type, but it also removes the Item content type that is initially associated with the list after the list has been created. Removing this unneeded content type helps eliminate confusion on the part of the user by removing the “Item” menu command from the list’s New menu and leaving only the “Company” menu command for creating new items in the list.

It is also important to note that when you add a content type to a list through the object model, WSS provisions columns for each content type field within the list. For example, when the Company content type is added to the new list, the Fields collection of the content type is copied locally to the list. As you might expect, changes made to fields within the list do not affect the Company content type. However, changes you make to the Company content type through the user interface can optionally be pushed down to all list instances where that content type is used.

In our example in which we added the Company content type to a generic list, there is one issue concerning the field named Title. Since the generic list already contained a field created from the Title site column, WSS did not add it. Therefore, the part of the Company content type definition that renamed this field with a DisplayName of "Company" does not have any effect. Therefore, we have added a few lines of code to rename the Title field to Company.

 SPField TitleField = list.Fields["Title"]; TitleField.Title = "Company"; TitleField.Update();

Although WSS automatically adds the fields of the Company content type to the new list, it does not add any of these fields to the list’s views. This all has to be done separately. For example, the All Items view of the generic list will show only the Title field until you obtain a reference to its SPView object and add the other fields through the view’s ViewFields property. Note that when adding fields to the ViewFields collection, it is important to reference the field by its Name as opposed to its DisplayName.

  SPView view = list.Views["All Items"]; view.ViewFields.Add("FullName"); view.ViewFields.Add("WorkPhone"); view.ViewFields.Add("Industry"); view.ViewFields.Add("CompanySize"); view.ViewFields.Add("ActivityNotes"); view.Update(); 

Now that we have created a new list and configured it to use the Company content type, let’s take the next step and add an item through the object model. When creating a new item through the object model, it’s possible to explicitly specify the content type, which is often required when dealing with lists that support multiple content types. This can be accomplished by creating and initializing an SPContentTypeId object and assigning that the new item’s internal field named “Content Type ID”, as shown in the following code:

  // create SPContentType instance string id = @"0x0100E71A2716C18B4e96A9B0461156806FFA"; SPContentTypeId CompanyTypeID = new SPContentTypeId(id); // create new item using that content type SPListItem item = list.Items.Add(); item["Content Type ID"] = CompanyTypeID; item["Title"] = "Fabrikam"; item["FullName"] = "Mike Fitzmaurice"; item["WorkPhone"] = "(425)111-2222"; item["Industry"] = "High Tech"; item["CompanySize"] = "1-20"; item["ActivityNotes"] = "This Fitz guy has great widgets"; item.Update(); 

Additional Content Type Metadata

The CAML-based definition for a content type also can contain embedded XML documents that carry additional metadata about the content type. This metadata can be either custom data unique to your application or data within a WSS schema. You can include any XML content you like in the XmlDocument node as long as it is valid XML. The main use of the XmlDocument nodes within WSS itself is to specify custom data forms and event handlers. The following example shows a custom data form specification, in which the display, edit, and new forms are custom-defined. Notice that the contenttype/forms namespace URI defines the behavior.

 <XmlDocuments>   <XmlDocument        NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">     <FormTemplates          xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">       <Display>CompanyData.aspx</Display>       <Edit>EditCompanyData.aspx</Edit>       <New>NewCompanyData.aspx</New>     </FormTemplates>   </XmlDocument> </XmlDocuments>

Defining Content with List Schemas

Lists can be provisioned through the user interface, through the WSS object model as we just saw, or through provisioning components deployed in a feature. The provisioning component for defining a list type in WSS is the List Schema, which is created with CAML. Note that you also could package and deploy a list schema directly in a site definition, but this is not the recommended deployment strategy, because it eliminates the possibility of using the list schema within an existing site.

The LitwareTypes feature that we have used throughout this chapter contains an element manifest named VendorList.xml. This element manifest contains a ListTemplate element that provides the starting point for creating a list schema for a new list type named VendorList.

 <ListTemplate   Name="VendorList"   Type="10001"   BaseType="0"   VersioningEnabled="True"   Hidden="false"   Sequence="2000"   DisplayName="Litware Vendors"   Description="Create a custom vendor list to track companies."   Image="/_layouts/images/itcontct.gif" />

As you can see, this ListTemplate element has a Name attribute that has a value of VendorList. The Name within a ListTemplate element is important because there must be a directory inside the feature directory with the same name that contains a file with the well-known name schema.xml. Within the LitwareTypes feature directory, you should be able to locate a directory named VendorList that contains the required schema.xml file. The heart of the list schema for the VendorList type is defined inside this schema.xml file.

In addition to the Name, the ListTemplate element also defines attributes such as Type, BaseType, VersioningEnabled, Hidden, Sequence, DisplayName, Description, and Image. Note that the Type attribute defines a list type identifier and by convention should be given a value of 10,000 or higher for custom list types. The BaseType attribute value 0 is used to indicate that this is a list type for standard lists. The Hidden attribute is given a value of false so that this list type shows up on the standard Create page, enabling users to create instances of this list type through the standard user interface. The Sequence attribute value is used to position the new list type’s link on the Create page.

Note that developing list schemas is much more complicated than developing content types. Although the content type defines only the data schema and behavior, the list schema inside a schema.xml file must define content type references and its own separate data schema as well as views and forms. The section of the schema.xml file that defines the views is typically several thousand lines in length because of all the required CAML rendering instructions. Therefore, the entire schema.xml file used to define the VendorList type is far too long to display as a listing in a book such as this. Listing 6-11 shows the skeleton of the schema.xml file used to define the VendorList type.

Listing 6-11: The skeleton of the schema.xml file for the VendorList type

image from book
  Vendors List Schema Skeleton <List     Title="Vendors"     Url="Lists/Vendors"     BaseType="0"     DisableAttachments="True"     VersioningEnabled="True"     xmlns="http://schemas.microsoft.com/sharepoint/" >   <MetaData>     <ContentTypes> <!-- add content types --> </ContentTypes>     <Fields> <!-- add fields --> </Fields>     <Views> <!-- define views here --> </Views>     <Forms> <!-- add support for forms here --> </Forms>   </MetaData> </List> 
image from book

Now let’s examine individual sections of the schema.xml file in more detail. We will begin with the section named ContentTypes. This section is used to add references to the content types that will be used for the list. In our case, we will add a reference to the Vendors content type, and we’ll also add a reference to the standard content type for Folders, which has an ID of 0x0120.

 <ContentTypes>   <ContentTypeRef >     <Folder TargetName="Vendors" />   </ContentTypeRef>   <ContentTypeRef  /> </ContentTypes>

Next, we will examine the Fields section. Dealing with fields from one or more content types in a list schema is more complicated than the example shown earlier, when we added a content type to a list through the object model. When you add a content type through the object model, WSS automatically adds the fields of the content type into the Fields collection of the list. This doesn’t happen automatically when you add a content type reference in schema.xml. You still must explicitly add each field by using its ID and include other attributes such as its Name and Type. Listing 6-12 shows what the Fields element looks like inside the schema.xml file for the VendorList type.

Listing 6-12: The list schema within the schema.xml file defines a Fields collection.

image from book
  <Fields>   <Field           Name="Title" Type="Text" Sealed="TRUE" DisplayName="Company"          Source />   <Field           Name="LinkTitle" DisplayName="Company" Sealed="TRUE"          Source />   <Field           Name="LinkTitleNoMenu" DisplayName="Company" Sealed="TRUE"          Source />   <Field           Name="FullName" Type="Text" DisplayName="Contact"          Source />   <Field           Name="WorkPhone" Type="Text" DisplayName="Phone"          Source />   <Field           Name="Industry" DisplayName="Industry"          Type="Choice" Format="RadioButtons"          Source >     <CHOICES>       <CHOICE>High Tech</CHOICE>       <CHOICE>Legal</CHOICE>       <CHOICE>Medical</CHOICE>     </CHOICES>     <Default>High Tech</Default>   </Field>   <Field           Name="CompanySize" Type="CompanySize" DisplayName="Company Size"          Source />   <Field           Name="ActivityNotes" DisplayName="Activity Notes"          Type="Note" RichText="TRUE" AppendOnly="TRUE" Sortable="FALSE"          Source /> </Fields> 
image from book

When developers first work with schema.xml files, the requirement to add redundant field definitions into the list schema doesn’t seem too intuitive. After all, we’ve already defined the fields once in the Company content type, so why should we be forced to define them a second time in the list schema? WSS, however, doesn’t supply any mechanism to copy the fields from content types that are referenced from inside the schema.xml file.

If you are creating a list schema that references one or more content types, you should include all fields from all the referenced content types. You can think about a list schema as a data store that provides storage for one or more well-defined item schemas, each of which may or may not use each field defined in the list schema’s Fields collection.

For large sets of data, the list schema can define indexes on fields that can significantly improve data access and querying list content. This makes lists a viable storage mechanism for external business applications with the added value of the collaborative interface of WSS. Indexed fields can be defined in the list schema, or they can be specified after creation through the Web interface or the object model. List indexes are similar to the concept of a SQL index, although the index is defined in a SQL-indexed name-value table that contains a reference to the list as well as the indexed column values.

After adding fields, you also will want to specify the fields used in the various views supported by the list, such as the standard All Items view. The view section itself defines one or more views and is fairly cumbersome to work with due to all the required CAML rendering instructions. One of the easiest ways to create the views for a new schema.xml file is to copy the Views node from the Custom List feature (12\TEMPLATE\FEATURES\CustomList\CustList\Schema.xml) and insert it into your custom list schema.

After copying the Views node from an existing list schema such as the one for custom lists, you can then search for the ViewFields node for each View node and insert your custom field references. The following code listing displays the ViewFields node that is used in each of the View elements defined inside the schema.xml file of the list schema:

  <ViewFields>   <FieldRef              Name="LinkTitle" />   <FieldRef              Name="FullName" DisplayName="Contact" />   <FieldRef              Name="WorkPhone" DisplayName="Phone" />   <FieldRef              Name="Industry" DisplayName="Industry" />   <FieldRef              Name="CompanySize" DisplayName="Company Size" />   <FieldRef              Name="ActivityNotes" DisplayName="Activity Notes" /> </ViewFields> 

A list schema can optionally be written to use custom forms for viewing and editing content. The default list schemas use standard form pages defined in 12\TEMPLATE\Pages. These forms display content using CAML rendering instructions maintained in the Views node. This is what we have done with the custom list schemas defined inside the LitwareTypes feature. However, you also can specify custom form pages, or you can use custom form pages defined by a content type. If a custom form is not specified, the form page “form.aspx” is instantiated into three Web Part pages in the WSS site. It has one WebPartZone that is populated with an instance of the ListFormWebPart, which uses the appropriate list view defined in schema.xml.

Creating a List Instance

Now that we have created the VendorList type and its associated list schema, it’s time to create an instance of this list. Earlier in this chapter, you saw how to do this using the object model. This time we will create a new list instance by adding a CAML element to the LitwareTypes feature. In particular, you can accomplish this by adding a ListInstance element with the following attribute settings:

  <ListInstance     TemplateType="10001"          Title="Vendors"     Url="Vendors"     Description="Litware Vendors"     OnQuickLaunch="True" >   <Data>     <Rows>       <Row>         <Field Name="ContentType">Company</Field>         <Field Name="Title">Acme Corp, Inc</Field>         <Field Name="FullName">Bob Jones</Field>         <Field Name="WorkPhone">(813)345-5432</Field>         <Field Name="Industry">High Tech</Field>         <Field Name="CompanySize">101-1000</Field>         <Field Name="ActivityNotes">These folks have some super fine gadgets</Field>       </Row>     </Rows>   </Data> </ListInstance> 

This example also demonstrates how you can use CAML to add new items to a list declaratively. A Data node can be created with one or more Row nodes. Within each Row node, you can add Field elements to assign a value to each column including the content type.

Configuring Lists with RSS Feeds

Really Simple Syndication (RSS) is an XML specification for content distribution that is simple to generate, easy to consume, and an open format for integration. Although there are several specifications for content syndication, RSS is the simplest and most widely used. Listing 6-13 displays XML from an announcements list RSS feed. The RSS XML stream defines a list of recurring items, where each item has a title, description, link, and author. The RSS document contains an RSS channel as well that defines information about the feed. This data specification makes RSS a natural fit for WSS Lists and list items, which also have the same corresponding information. Each list item has a link that points to the item’s display page, unique identifier, and title. The description RSS field is the only field that SharePoint has to make assumptions on, and this is configured through List Settings.

Listing 6-13: An announcements RSS feed, autogenerated by Windows SharePoint Services

image from book
  An Announcements RSS Feed <?xml version="1.0" encoding="UTF-8"?> <rss version="2.0">   <channel>     <title>Litware Teams: Announcements</title>     <link>http://litwareinc.com/Announcements/AllItems.aspx</link>     <description>RSS feed for the Announcements list.</description>     <lastBuildDate>Mon, 20 Nov 2006 06:53:29 GMT</lastBuildDate>     <generator>Windows SharePoint Services V3 RSS Generator</generator>     <ttl>60</ttl>     <image>       <title>Litware Teams: Announcements</title>       <url>/_layouts/images/homepage.gif</url>       <link>http://litwareinc.com/Announcements/AllItems.aspx</link>     </image>     <item>       <title>Stay connected with Windows SharePoint Services!</title>       <link>http://litwareinc.com/Announcements/DispForm.aspx?ID=1</link>       <description><![CDATA[Microsoft Windows SharePoint Services helps you to be more                     effective by connecting people, information, and documents with                     the syndication power of RSS!]]>       </description>       <author>Mike Fitzmaurice</author>       <pubDate>Sun, 19 Nov 2006 23:18:27 GMT</pubDate>       <guid isPermaLink="true">         http://litwareinc.com/Announcements/DispForm.aspx?ID=1       </guid>     </item>   </channel> </rss> 
image from book

The RSS feed for a WSS list is security trimmed; this means that if a list item has security applied to it that prevents a user from seeing the item from the list view, the user will not be able to see it from the RSS view either. The RSS feed also can be accessed from code using the object model, where the list has a method called WriteRssFeed that generates the XML based on list settings and the credentials of the current thread’s security context.

Tip 

To ensure the integrity of security and authorization, WSS code uses the security context of the user that is making the Web request or executing the current code. As long as your code impersonates the user and doesn’t run with escalated credentials, you can be sure that data in SharePoint is secure!

The RSS feed is not meant to be an authoritative list of the content of a given list or document library. Rather, it is intended for timely updates through loosely coupled integration so that users can keep up with content without directly viewing the list. Often the feed consumer is an enterprise news aggregator such as NewsGator Enterprise Server, Feed Demon, Microsoft Office Outlook, or another SharePoint site.




Inside Microsoft Windows Sharepoint Services Version 3
Inside Microsoft Windows Sharepoint Services Version 3
ISBN: 735623201
EAN: N/A
Year: 2007
Pages: 92

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net