May 2009

Volume 24 Number 05

Office Space - Custom Field Types for SharePoint 2007

By Ted Pattison | May 2009

Code download available

Contents

What Exactly Is a WSS Field Type?
Data Validation
Multicolumn values
Custom Property Fields

As the adoption of Microsoft SharePoint 2007 continues to accelerate with both business users and developers, one has to attribute much of its success to how quickly users can create lists within their sites. Windows SharePoint Services (WSS) 3.0 provides many useful built-in list types that are used to track common forms of business data such as tasks, scheduled events, and announcements. WSS also provides users with the flexibility to add and remove columns from a list to handle scenarios where the built-in WSS list types do not provide an exact match to the business problem at hand.

When you create a new column inside a particular list instance and configure it for a specific business scenario, it can be hard or impossible to reuse your efforts across multiple lists. Fortunately, the latest version of WSS introduced reusable column types known as site columns. Advanced users can create a site column in the site column gallery of a top-level site. This site column can be configured a single time and then added to many different lists across the scope of the current site collection. Developers can extend this scope of reuse by defining a site column inside a feature, which can then be reused across site collections and across SharePoint farms.

While site columns provide users and developers with new capabilities in reuse, there is a way to define a reusable column definition that is even more powerful. WSS allows developers to drop down to a lower level by creating custom field types. These will be the focus of this month's column. Once you learn how to create a custom field type, you will have the greatest amount of control that WSS offers when it comes to creating a polished user interface for your users to display and edit column values. Developing custom field types also provides a powerful new means of performing data validation before allowing user input values to be written into the content database.

What Exactly Is a WSS Field Type?

When users want to create a new column and add it to a list or document library, they must select an underlying field type on which to base this new column. Likewise, a user or developer must also select an underlying field type when creating a new site column. Figure 1shows an example of the field types that are available out-of-the-box when you are creating a new list column within a farm running Microsoft Office SharePoint Server (MOSS) 2007.

fig01.gif

Figure 1 Base Field Types Available in SharePoint

As you can see, the list of field types made available to users can be extended through the development of custom field types. In this month's column, I will show you how to do this as well as how to create a solution package to distribute and deploy your custom field types that follows best practices in SharePoint development.

To create a custom field type, you can create a project in Visual Studio that contains the following items:

  • An ASP.NET user control that defines an editing surface known as a RenderingTemplate
  • A public class to initialize and manage the RenderingTemplate
  • A public class that defines the actual custom field type
  • An XML file that is used to deploy the custom field type

Figure 2shows a screenshot of the sample project named OfficeSpaceFieldTypes created with Visual Studio. This project is included as a download to accompany this month's column. This project contains four custom field types, including FieldHelloWorld, FieldSocialSecurityNumber, FieldUnitedStatesAddress, and FieldEmployeeStatus.

fig02.gif

Figure 2 Visual Studio Project to Develop Custom Field Types

I am going to begin by stepping through the custom field type named FieldHelloWorld to show you how all the pieces fit together. Then I will examine a few more interesting aspects of the other three custom field types to illustrate some of the possibilities that might motivate you to develop custom field types of your own.

Note that the OfficeSpaceFieldTypes project was built for Visual Studio using the STSDEV utility. You can open it using either Visual Studio 2005 or Visual Studio 2008. For more background information on developing with the STSDEV utility, you can read my Office Space column in the March 2008 issue of MSDN Magazinetitled " Simplify SharePoint Development with STSDEV."

The user control file named OfficeSpace.HelloWorld.ascx contains a RenderingTemplate control tag that acts as the editing surface for a custom field type:

<%@ Control Language="C#" Debug="true" %> <%@ Assembly Name="Microsoft.SharePoint, [full 4-part name]" %> <%@ Register TagPrefix="SharePoint" Assembly=" Microsoft.SharePoint, [full 4-part name]" Namespace="Microsoft.SharePoint.WebControls" %> <SharePoint:RenderingTemplate ID="HelloWorldRendingTemplate" runat="server"> <Template> <asp:TextBox ID="txtUserInput" runat="server" CssClass="ms-long" /> </Template> </SharePoint:RenderingTemplate>

The top-level control tag is based on the RenderingTemplate control, which is defined inside the Microsoft.SharePoint assembly in the Microsoft.SharePoint.WebControls namespace. I have given the RenderingTemplate control tag an ID value of HelloWorldRendingTemplate. Later you will see how to use this string-based ID value to load and initialize the RenderingTemplate control from the .ascx file.

Inside the control tag for the RenderingTemplate control, there is an inner Template element. You create the editing surface for a custom field type by adding ASP.NET control tags and HTML inside this Template tag. You have the flexibility to create the editing surface for a custom field type using a composite of ASP.NET controls and to lay them out using div elements or an HTML table. I have defined our first sample using a simple ASP.NET TextBox control:

<asp:TextBox ID="txtUserInput" runat="server" CssClass="ms-long" />

If you look back at the Visual Studio project structure in Figure 2, you can see that all four .ascx files have been deployed inside the WSS RootFiles directory within the directory structure of TEMPLATE/CONTROLTEMPLATES. This is the directory where WSS requires you to deploy any .ascx file that defines a RenderTemplate control that is to be used by a custom field type.

As a general best practice in SharePoint 2007 development, it is best to avoid deploying your files directly inside one of the WSS system directories, such as CONTROLTEMPLATES, IMAGES, or LAYOUTS. Instead, you should create a solution-specific directory inside one of these WSS system directories for deploying your files so that you can avoid potential naming conflicts with files deployed by Microsoft and by other third-party developers.

However, the technique of creating a solution-specific directory inside the CONTROLTEMPLATES directory does not work correctly when dealing with the .ascx file that defines a RenderingTemplate control. This .ascx file must be deployed directly inside the CONTROLTEMPLATES directory because that is the only directory in which the WSS runtime looks, when it loads .ascx files, to discover the list of available RenderingTemplate controls during its initialization of the hosting worker process.

In just a moment, I will show you how to create the class that will be used to define a custom field type. However, I want to first discuss the other class required to create a custom field type, which is known as the field control class. The field control class will be used to load and initialize the RenderingTemplate control from the .ascx file.

You create a field control class by creating a public class that inherits from a base class from the WSS object model named BaseFieldControl. When you create a field control class, you must override a read-only property named DefaultTemplateName that allows you to pass the ID of the RenderingTemplate control to the WSS runtime:

public class HelloWorldFieldControl : BaseFieldControl { // pass ID of RenderingTemplate control to WSS runtime protected override string DefaultTemplateName { get { return "HelloWorldRendingTemplate"; } } }

Once you have implemented the DefaultTemplateName property, you should then add a protected field to track a reference to each control inside the RenderingTemplate that you want to access programmatically. Next, you should override the CreateChildControls method to properly initialize these fields:

public class HelloWorldFieldControl : BaseFieldControl { // add field to reference control protected TextBox txtUserInput; // initialize field with reference to control protected override void CreateChildControls() { base.CreateChildControls(); txtUserInput = (TextBox)TemplateContainer.FindControl("txtUserInput"); } }

Note that you will not be instantiating control instances in the CreateChildControls method, but rather going through a protected property of the base class named TemplateContainer, which exposes a FindControl method. This technique allows you to obtain references to existing control instances.

Now it's time to add the code to the field control class that is responsible for reading and writing the field's underlying value to and from the content database. You do this by overriding a property of the BaseFieldControl class named Value. The Value property is based on the type System.Object, which allows quite a bit of flexibility. You can work with any type of object that you'd like as long as it supports .NET serialization. The HelloWorldFieldControl class illustrates a simple example of implementing the Value property:

public class HelloWorldBaseFieldControl : BaseFieldControl { public override object Value { get { this.EnsureChildControls(); return txtUserInput.Text; } set { this.EnsureChildControls(); txtUserInput.Text = (string)this.ItemFieldValue; } } }

As you can see, both the get and set methods of the Value property begin their implementation with a call to the EnsureChildControls. A call to EnsureChildControls ensures that the CreateChildControls method has already executed. This is required to ensure that the txtUserInput field contains a valid reference so that you can program against the control without a null reference exception.

You can see that the get method of the Value property simply returns the string value from the TextBox control. The WSS runtime will call the get method when a user updates an item that contains a column based on this custom field type. The WSS runtime writes your return value directly to the content database.

The WSS runtime calls the set method when a user opens the item in edit view just before the controls in your RenderingTemplate control are shown to the user. The key point to understand about implementing the set method is that the ItemFieldValue property provides you with access to the current column value and you must use this value to initialize the control (or controls) in your RenderingTemplate.

Now that I have walked through the implementation of the field control class, let's move on to the class that defines the actual custom field type itself. Begin by creating a public class that inherits from one of the WSS built-in field types such as SPFieldText, SPFieldNumber, SPFieldDateTime, or SPFieldMultiColumn. The class created for the first example named FieldHelloWorld inherits from SPFieldText:

public class FieldHelloWorld : SPFieldText { // custom field type implementation goes here }

The first thing you must do when implementing the class for a custom field type is add two standard public constructors. These nondefault constructors are required because the WSS runtime will use them to create instances from your custom field type. You don't need to supply any code inside the curly braces. You just need to define parameters and pass them on to the public constructor of the base class with a matching parameter list:

public class FieldHelloWorld : SPFieldText { // add public constructors to call base class constructors public FieldHelloWorld( SPFieldCollection fields, string fieldName) : base(fields, fieldName) { } public FieldHelloWorld(SPFieldCollection fields, string typeName, string displayName) : base(fields, typeName, displayName) { } }

Once you have added the two standard public constructors, you next override a read-only property named FieldRenderingControl. You implement the get method of this property by creating an instance of the field control class and initializing its FieldName property using the InternalName property of the custom field type class. The return value of the get method should be a reference to this newly created instance:

public override BaseFieldControl FieldRenderingControl { get { BaseFieldControl ctr = new HelloWorldFieldControl(); ctr.FieldName = this.InternalName; return ctr; } }

When creating the class for a custom field type, you also have the option of overriding the get method of the DefaultValue property so that you assign a new value to a column when a user creates a new item:

public override string DefaultValue { get { return "Hello World (default value)"; } }

So far, I created an ASP.NET User Control (an .ascx file) that defines a RenderingTemplate control, and I created two public classes for the field control and the custom field type. Note that these two public classes must be compiled into an assembly with a strong name and this assembly must be installed in the Global Assembly Cache (GAC). The final step in deploying and testing your development efforts is to create an XML file using a special naming scheme and place it in a well-known location where the WSS runtime can find it.

When you create the XML file used to deploy a custom field type, you must start its name with the string "fldtypes" and you must give the file an extension of .xml. The XML file used in the OfficeSpaceFieldTypes project is fldtypes_OfficeSpace.xml. As you can see in Figure 2, the file must also be deployed within the WSS RootFile directory structure at a location of TEMPLATE/XML.

A key point to understand is that the WSS runtime searches the TEMPLATE/XML directory when it initializes the hosting worker process to find any file that matches the pattern of fldtypes*.xml. The WSS runtime then reads XML data from each file that matches this pattern and uses this data to determine what field types are currently active and in use on a farmwide basis.

If you look in the TEMPLATE/XML directory, you will find a system file named fldtypes.xml that defines all the core field types supplied by WSS. The installation of MOSS deploys several more files into the TEMPLATE/XML directory, such as fldtypes_publishing.xml and fldtypes_spsbizdata.xml, to supply additional custom field types of its own. By inspecting these files that are part of the standard SharePoint 2007 installation process, you can see how the WSS team and the MOSS team have structured the XML data for built-in field types, which might teach you a few tricks you can use when creating your own.

An XML file used to deploy custom field types such as fldtypes_OfficeSpace.xml requires a top-level FieldTypes element. You will add one inner FieldType element for each of custom field type you would like to deploy:

<FieldTypes> <FieldType> <!—add field type data here --> </FieldType> <FieldTypes>

The FieldType element will contain several Field elements with a Name attribute that defines the type of value inside. Here is the XML used to define the FieldType element for the custom field type named FieldHelloWorld:

<FieldType> <Field Name="TypeName">HelloWorld</Field> <Field Name="ParentType">Text</Field> <Field Name="TypeDisplayName"> Hello World (Office Space Demo)</Field> <Field Name="TypeShortDescription"> Hello World (Office Space Demo)</Field> <Field Name="UserCreatable">TRUE</Field> <Field Name="FieldTypeClass"> [field type class name],[4-part assembly name]</Field> </FieldType>

Now let's examine selected implementation details from the three other custom field types to show you how to get a little more involved.

Data Validation

The three other custom field types defined in the OfficeSpaceFieldTypes project have been designed for a scenario in which you need to collect data about employees for a SharePoint list. Figure 3shows the edit view for an item in a list that uses the custom field types named FieldSocialSecurityNumber, FieldUnitedStatesAddress, and FieldEmployeeStatus. These three field types demonstrate various techniques, such as adding validation logic, working with multicolumn values, and adding custom properties.

fig03.gif

Figure 3 OfficeSpaceFieldTypes captures employee data in a SharePoint list

The custom field type named FieldSocialSecurityNumber provides an example of how to perform custom data validation. This is done by overriding a method named GetValidatedString within the class that defines the custom field type. The implementation of GetValidatedString in FieldSocialSecurityNumber has been designed to ensure the user input matches the pattern for a social security number using regular expressions:

public override string GetValidatedString(object value) { string UserInput = value.ToString(); string SSN_RegularExpression = @"^\d{3}-\d{2}-\d{4}$"; if ( (this.Required || !string.IsNullOrEmpty(UserInput) ) & (!Regex.IsMatch(UserInput, SSN_RegularExpression) ) ) { throw new SPFieldValidationException( "SSN must be form of 123-45-6789"); } return base.GetValidatedString(value); }

As you can see, the implementation tests for a regular expression match using the Regex class that is supplied as part of the Microsoft .NET Framework. The implementation also contains logic to test whether a column based on FieldSocialSecurityNumber has been defined to require the user to add a social security number or the field value can be left blank. However, if the field value is not required, the validation still requires that the user either leave the field value empty or provide a valid social security number.

If your validation logic determines that the user input is invalid, you simply throw an exception of type SPFieldValidationException. WSS responds by canceling the users request to save the current item and displays your error message directly to the user as shown in Figure 4.

fig04.gif

Figure 4 Custom validation logic prevents invalid data

Multicolumn values

The custom field type named FieldUnitedStatesAddress demonstrates the technique of creating a field type with multicolumn values. This is useful in the case of capturing an address from the user where there are several pieces of related data that must be captured and validated as a whole. You can refer back to Figure 3to see what this custom field looks like when a user is in edit view for an item.

One requirement of multicolumn field types such as FieldUnitedStatesAddress is that they must inherit from the SPFieldMultiColumn. In most cases, there will also be a requirement for your RenderingTemplate to display more than one input control to the user. Figure 5shows an example of the RenderingTemplate used by the custom field type named FieldUnitedStatesAddress.

Figure 5 RenderingTemplate for Multicolumn Values

<SharePoint:RenderingTemplate runat="server" ID="UnitedStatesAddressRenderingTemplate" > <Template> <table class="ms-authoringcontrols" > <tr> <td>Street:</td> <td><asp:TextBox ID="txtStreet" runat="server" /></td> </tr> <tr> <td>City:</td> <td><asp:TextBox ID="txtCity" runat="server" /></td> </tr> <tr> <td>State:</td> <td><asp:TextBox ID="txtState" runat="server" /></td> </tr> <tr> <td>Zipcode:</td> <td><asp:TextBox ID="txtZipcode" runat="server" /></td> </tr> </table> </Template> </SharePoint:RenderingTemplate>

When you create a multicolumn field type, you typically create the RenderingTemplate as a composite of several input controls like the example you have just seen. Next, you need to learn the trick of moving the values from these controls back and forth to and from the content database as a single multicolumn value. The WSS object model supplies a creatable class type named SPFieldMultiColumnValue that makes this possible using programming syntax similar to dealing with a string array. The Value property implementation of UnitedStatesAddressFieldControl shows the common pattern of reading and writing multicolumn values to and from the content database (see Figure 6).

Figure 6 Reading and Writing Multicolumn Values

public override object Value { get { this.EnsureChildControls(); SPFieldMultiColumnValue mcv = new SPFieldMultiColumnValue(4); mcv[0] = txtStreet.Text; mcv[1] = txtCity.Text; mcv[2] = txtState.Text; mcv[3] = txtZipcode.Text; return mcv; } set { this.EnsureChildControls(); SPFieldMultiColumnValue mcv = (SPFieldMultiColumnValue)this.ItemFieldValue; txtStreet.Text = mcv[0]; txtCity.Text = mcv[1]; txtState.Text = mcv[2]; ; txtZipcode.Text = mcv[3]; } }

It would be possible for you to extend this simple example further to exploit the potential of multicolumn field types. For example, what if you were able to call to a Web service and pass a ZIP code that would return the associated city and state? That would allow you to add extra functionality to auto-populate the textboxes for city and state and to perform validation to ensure that the address is correct.

Custom Property Fields

The custom field type named FieldEmployeeStatus demonstrates how you can extend a custom field type with one or more custom property fields. You can accomplish this by adding a PropertySchema element to the bottom of the FieldType element for a specific custom field type in the fldtype.xml file. The custom field type named FieldEmployeeStatus has been defined with two custom property fields named AllowContactors and AllowInterns (see Figure 7).

Once you have added custom property fields in this manner, WSS will automatically add input controls to the page that allows a user to add or update columns based on your custom field type. Figure 8shows what the user sees when adding or updating a column based on the custom field type named FieldEmployeeStatus. The user has the option of choosing to include or exclude choice values for adding list items that are contractors or interns.

Figure 7 Custom Property Fields

<FieldType> <Field Name="TypeName">EmployeeStatus</Field> <Field Name="ParentType">Text</Field> <!—other Field elements omitted for clarity --> <PropertySchema> <Fields> <Field Name="AllowContractors" DisplayName="Allow for Contractors" Type="Boolean"> <Default>0</Default> </Field> <Field Name="AllowInterns" DisplayName="Allow for Interns" Type="Boolean"> <Default>0</Default> </Field> </Fields> </PropertySchema> </FieldType>

fig08.gif

Figure 8 Custom Field Types with Custom Property Fields

Once you have extended a custom field type with one or more custom property fields, you then must write code through the WSS object model to see what values the user has assigned to them. In the case of the custom field type named FieldEmployeeStatus, there is code in the CreateChildControls method of the field control class that initializes the RadioButtonList control from the RendingTemplate that adds items for contractors and interns only when it sees that the user has set the values for the associated custom property fields to true (see Figure 9).

Figure 9 Conditional Display of Property Fields

protected override void CreateChildControls() { base.CreateChildControls(); lstEmployeeStatus = (RadioButtonList)TemplateContainer.FindControl("lstEmployeeStatus"); if (lstEmployeeStatus != null) { lstEmployeeStatus.Items.Clear(); lstEmployeeStatus.Items.Add("Full-time Employee"); lstEmployeeStatus.Items.Add("Part-time Employee"); // check to see if contractors are allowed bool AllowContactors = (bool)this.Field.GetCustomProperty("AllowContractors"); if (AllowContactors) { lstEmployeeStatus.Items.Add("Contractor"); } // check to see if interns are allowed bool AllowInterns = (bool)this.Field.GetCustomProperty("AllowInterns"); if (AllowInterns) { lstEmployeeStatus.Items.Add("Intern"); } } }

When a user adds a new column to a list based on the custom field type named FieldEmployeeStatus and assigns a value of true, a user adding and updating items inside the list will now see additional choices in the RadioButtonList control.

You can see that this style of development can be quite powerful when it comes to handling how users input data and data validation. It might also give you new ideas about storing types of data inside standard SharePoint lists that you might not have thought were practical.

Send your questions and comments to mmoffice@microsoft.com.

Ted Pattison is an author, trainer, and SharePoint MVP who lives in Tampa, Florida, and whose latest book is Inside Windows SharePoint Services 3.0 from Microsoft Press. He also delivers advanced SharePoint training to professional developers through his company, Ted Pattison Group.