Walkthrough: Creating a Custom Field Type
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
This topic provides a step-by-step guide to creating a custom field type in the most common situation: the rendering of the field on a list view and in Display mode is handled by a RenderPattern in the field definition file, while field rendering in both New and Edit modes is handled by a single rendering template that works together with the CreateChildControls method. You will create a field that is intended to hold 10-digit International Standard Book Number (ISBN).
Prerequisites
This walkthrough requires that you have the following applications installed on a computer that is running the Windows Server 2003 operating system:
Windows SharePoint Services 3.0
Microsoft Visual Studio 2005
Microsoft Visual Studio 2005 extensions for Windows SharePoint Services 3.0, Version 1.0 or later
To prepare for development of custom field types
In Visual Studio, select External Tools from the Tools menu.
In the External Tools dialog, click Add and enter Get Assembly Public Key for the Title.
Fill the Command textbox by browsing to sn.exe. It is typically installed at the following location: C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\sn.exe.
In the Arguments textbox, type the following (case sensitive) -Tp "$(TargetPath)".
Enable the Use Output window checkbox.
Click OK. The new command is added to the Tools menu.
Setting Up the Project
To set up the custom field project
In Visual Studio 2005, select New Project from the File menu.
On the New Project dialog, select SharePoint in the Project Types window. (If there is no SharePoint project option in the window, you need to install Visual Studio 2005 Extensions for Windows SharePoint Services 3.0, Version 1.0 or later.)
Select Empty in the Templates window.
In the Name box, type ISBN_Field_Type.
Click OK.
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Properties, then the Build Events tab.
Type the following in the Post-build event command line box. These lines will ensure that each time you rebuild the project, the latest version of the files in your project are copied to the correct location and Windows SharePoint Services 3.0 is restarted so it will load the latest version of the assembly.
cd "$(ProjectDir)" "%programfiles%\microsoft visual studio 8\sdk\v2.0\bin\gacutil" /i "$(TargetPath)" /nologo /f %systemroot%\system32\iisapp.vbs /a "SharePoint_App_Pool" /r xcopy *.ascx "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES\" /y xcopy fldtypes*.xml "C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\XML\" /y
Replace SharePoint_App_Pool with the actual name of the Internet Information Server (IIS) Application Pool that is assigned to your Windows SharePoint Services 3.0 Web Application. This is usually the same name as the IIS Web Site that holds the application; for example, "SharePoint - 80". (The quotation marks can be omitted if there are no spaces in the name.)
Click in any other enabled control on the tab so that Visual Studio detects your changes and an asterisk appears on the tab label, then select Save All from the File menu.
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Add, then New Item.
On the Add New Item dialog, select SharePoint in the Categories window and then Field Control in the Templates window.
In the Name box, type ISBN and click Add. This creates two files that you will edit in a later step: ISBN.Field.cs and ISBN.FieldControl.cs. A strong name is also created and stored in a file called Temporary.snk in your project.
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Properties.
Open the Application tab and change Assembly name to MyCompany.SharePoint.ISBN_Field_Type. (Throughout this walkthrough, replace MyCompany with the name of your company.)
Change Default namespace to MyCompany.SharePoint.
Click the Save all button on the toolbar.
In Solution Explorer, right-click the References node and select Add Reference.
On the Add Reference dialog, open the Browse tab and navigate to C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\.
Select PresentationFramework.dll and click OK. (This assembly contains the definition of the ValidationRule class that is referenced in the file that you create in the next procedure.)
Creating the Validation Rule Class
To create a validation rule class
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Add, then Class.
Select Visual C# Project Items in the Categories window and then Class in the Templates window.
In the Name box, type ISBN10ValidationRule.cs and click Add.
In the ISBN10ValidationRule.cs file that is created, add these using statements:
using System.Text.RegularExpressions; using System.Windows.Controls; using System.Globalization;
Change the namespace to conform to the guidelines in Namespace Naming Guidelines. In this walkthrough, use MyCompany.System.Windows.Controls.
Replace the class declaration with the following code:
public class ISBN10ValidationRule : ValidationRule { private const Int32 ISBNMODULO = 11; public override ValidationResult Validate(object value, CultureInfo cultureInfo) { String iSBN = (String)value; String errorMessage = ""; Regex rxISBN = new Regex(@"^(?'GroupID'\d{1,5})-(?'PubPrefix'\d{1,7})-(?'TitleID'\d{1,6})-(?'CheckDigit'[0-9X]{1})$"); if (!rxISBN.IsMatch(iSBN)) { errorMessage = "An ISBN must have this structure:\n1-5 digit Group ID, hyphen, \n1-7 digit Publisher Prefix, hyphen, \n1-6 digit Title ID, hyphen, \n1 Check Digit (which can be \"X\" to indicate \"10\").\n"; } if (errorMessage == "") // Matched the RegEx, so check for group length errors. { Match mISBN = rxISBN.Match(iSBN); GroupCollection groupsInString = mISBN.Groups; String groupID = groupsInString["GroupID"].Value; String pubPrefix = groupsInString["PubPrefix"].Value; if ((groupID.Length + pubPrefix.Length) >= 9) { errorMessage = "The Group ID and Publisher Prefix can total no more than 8 digits.\n"; } String titleID = groupsInString["TitleID"].Value; if (((groupID.Length + pubPrefix.Length) + titleID.Length) != 9) { errorMessage = errorMessage + "The Group ID, Publisher Prefix, and \nTitle ID must total exactly 9 digits.\n"; } if (errorMessage == "") //No group length errors, so verify the check digit algorithm. { Int32 checkDigitValue; String checkDigit = groupsInString["CheckDigit"].Value; // To ensure check digit is one digit, "10" is represented by "X". if (checkDigit == "X") { checkDigitValue = 10; } else { checkDigitValue = Convert.ToInt32(checkDigit); } String iSBN1st3Groups = groupID + pubPrefix + titleID; //Concatenate without the hyphens. // Sum the weighted digits. Int32 weightedSum = (10 * Convert.ToInt32(iSBN1st3Groups.Substring(0, 1))) + (9 * Convert.ToInt32(iSBN1st3Groups.Substring(1, 1))) + (8 * Convert.ToInt32(iSBN1st3Groups.Substring(2, 1))) + (7 * Convert.ToInt32(iSBN1st3Groups.Substring(3, 1))) + (6 * Convert.ToInt32(iSBN1st3Groups.Substring(4, 1))) + (5 * Convert.ToInt32(iSBN1st3Groups.Substring(5, 1))) + (4 * Convert.ToInt32(iSBN1st3Groups.Substring(6, 1))) + (3 * Convert.ToInt32(iSBN1st3Groups.Substring(7, 1))) + (2 * Convert.ToInt32(iSBN1st3Groups.Substring(8, 1))) + checkDigitValue; Int32 remainder = weightedSum % ISBNMODULO; // ISBN is invalid if weighted sum modulo 11 is not 0. if (remainder != 0) { errorMessage = "Number fails Check Digit verification."; } if (errorMessage == "") // Passed check digit verification. { return new ValidationResult(true, "This is a valid ISBN."); }// end check digit verification passed else // the check digit verification failed { return new ValidationResult(false, errorMessage); } }// end no group length errors else // There was some error in a group length { return new ValidationResult(false, errorMessage); } }// end RegEx match succeeded else // There was a RegEx match failure { return new ValidationResult(false, errorMessage); } }// end Validate method }// end ISBN10ValidationRule class
The validation rule class that you just created holds all of the detailed validation logic. For more information about validation rule classes, see System.Text.RegularExpressions and ValidationRule.
Creating the Custom Field Class
To create a custom field class
Open the ISBN.Field.cs file. A GUID, two required constructors, and an override of the FieldRenderingControl property have been provided. Only rarely will you need to modify these controls. By default, your new field class is declared to inherit from SPFieldText. For the purpose of this walkthrough, this is the correct choice. (For more information about the other classes from which you could derive your custom field type, see Custom Field Classes.)
Add the following using statements to ISBN.Field.cs. Be sure to change "MyCompany" to the name of your company:
using System.Windows.Controls; using System.Globalization; using MyCompany.SharePoint.WebControls; using MyCompany.System.Windows.Controls;
Change the namespace to MyCompany.SharePoint.
Add the following override of the GetValidatedString method to the ISBNField class:
public override string GetValidatedString(object value) { if ((this.Required == true) && ((value == null) || ((String)value == ""))) { throw new SPFieldValidationException(this.Title + " must have a value."); } else { ISBN10ValidationRule rule = new ISBN10ValidationRule(); ValidationResult result = rule.Validate(value, CultureInfo.InvariantCulture); if (!result.IsValid) { throw new SPFieldValidationException((String)result.ErrorContent); } else { return base.GetValidatedString(value); } } }// end GetValidatedString
This override illustrates a common pattern for overrides of GetValidatedString:
Overrides of the GetValidatedString method check whether the field is required and, if it is, the overridden method throws an SPFieldValidationException exception when the value is null or an empty String. This exception is caught by the New Item and Edit Item pages if the user attempts to save the list item that is being created or edited. In this case, the page remains open and the Message property of the exception causes an error message to appear beneath the empty field.
Overrides of GetValidatedString throw an SPFieldValidationException when the value is not valid, causing an error message to appear beneath the invalid field.
Overrides of GetValidatedString then call the base GetValidatedString, if the value passes the custom validation.
Save and close the file.
Creating the Field Rendering Control
To create the field rendering control
Open ISBN.FieldControl.cs. A GUID has been provided for the control. By default, your new field class is declared to inherit from TextField. For the purpose of this walkthrough, this is the correct choice. (For more information about other classes that custom field controls can be derived from, see Field Rendering Controls.)
Add the following using statement:
using System.Web.UI.WebControls;
Change the namespace to MyCompany.SharePoint.WebControls.
Add a protected field for an ASP.NET Label web control that will prefix "ISBN" before each ISBN number when it renders in New or Edit mode. There is no need to add a protected TextBox field to hold the ISBN number itself because the custom ISBNFieldControl is inheriting that field from TextField.
protected Label ISBNPrefix;
Next add the following override of the DefaultTemplateName property. The String that you are assigning to this property is the ID of a RenderingTemplate object in an .ascx file that you create in a later step and then deploy to the folder C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES. Since, in this example, we are not overriding ControlTemplate, Template, or TemplateName, the RenderingTemplate will be called like this: ControlTemplate will return Template, which will, in turn, return the Template property of whatever RenderingTemplate is named by TemplateName. Finally, the get accessor of TemplateName will return DefaultTemplateName. In a more complex case where, for example, you have separate templates for the New and Edit modes, you would need to override one or more of the preceding properties as well as probably the AlternateTemplateName or DefaultAlternateTemplateName properties.
protected override string DefaultTemplateName { get { return "ISBNFieldControl"; } }
Add the following override of the CreateChildControls method. The override does not perform any function if the underlying ISBNField is null. (It might be null if the ISBNFieldControl is created independently of the setter for the ISBNField's FieldRenderingControl property — see the override of FieldRenderingControl in ISBN.Field.cs.). The method also does not perform any function if the SPControlMode is Display. This is because Display mode rendering of the field is going to be handled, as it usually is, by a means of a RenderPattern element that is defined in a field definition that you create later in this walkthrough. (In other situations, Display mode rendering is accomplished by a special template identified by the DisplayTemplateName property. It is rare that CreateChildControls does anything in Display mode for a custom field type. (For more information about the different ways of rendering custom fields, see Patterns of Custom Field Rendering.)
protected override void CreateChildControls() { if (this.Field != null && this.ControlMode != SPControlMode.Display) { }// end if there is a non-null underlying ISBNField and control mode is not Display //Do nothing if the ISBNField is null or control mode is Display. }
Add the following call to the base method as the first line of the conditional. Such a call is usually necessary to ensure that the inherited child controls are created in case they are rendered entirely or partially by the base CreateChildControls instead of by a template. For example, the "TextField" template in DefaultTemplates.ascx (in C:\program files\common files\microsoft shared\web server extensions\12\template\controltemplates) renders the child TextBox, but the CreateChildControls method adjusts the maximum size of the TextBox to match the maximum size of the underlying SPFieldText field. The base CreateChildControls may also create dynamic BaseValidator controls. Ordinarily, however, you do not have access to the source code of the base method, so experimentation is needed to determine if it needs to be called and, if so, where it should be called in your override.
// Make sure inherited child controls are completely rendered. base.CreateChildControls();
Add the following lines to associate the child controls in the rendering template with the child control fields declared in your custom field control (or inherited by from its parent). You must do this now although the rendering template is something you will not create until a later step, because the call to the base CreateChildControls will associate the inherited child controls to the rendering template used by the parent of your custom field class, not to the custom rendering template, so the base association has to be replaced by a new one.
// Associate child controls in the .ascx file with the // fields allocated by this control. this.ISBNPrefix = (Label)TemplateContainer.FindControl("ISBNPrefix"); this.textBox = (TextBox)TemplateContainer.FindControl("TextField");
Add the following structure below the control association code. Your code should do nothing on a postback because reinitializing on a postback would cancel any changes a user has made to the values of the child controls.
if (!this.Page.IsPostBack) { }// end if this is not a postback //Do not reinitialize on a postback.
Inside the conditional structure you added in the last step, add the following inner conditional to initialize the TextBox child control with a default ISBN value when the control mode is New.
if (this.ControlMode == SPControlMode.New) { textBox.Text = "0-000-00000-0"; } // end assign default value in New mode
Nothing needs to be done in Edit mode because the OnLoad method will initialize ISBNFieldControl.Value to the value of ItemFieldValue which holds the current value of the field in the content database. At this point, your override of CreateChildControls should look like the following.
protected override void CreateChildControls() { if (this.Field != null && this.ControlMode != SPControlMode.Display) { // Make sure inherited child controls are completely rendered. base.CreateChildControls(); // Associate child controls in the .ascx file with the // fields allocated by this control. this.ISBNPrefix = (Label)TemplateContainer.FindControl("ISBNPrefix"); this.textBox = (TextBox)TemplateContainer.FindControl("TextField"); if (!this.Page.IsPostBack) { if (this.ControlMode == SPControlMode.New) { textBox.Text = "0-000-00000-0"; } // end assign default value in New mode }// end if this is not a postback //Do not reinitialize on a postback. }// end if there is a non-null underlying ISBNField and control mode is not Display // Do nothing if the ISBNField is null or control mode is Display. }
Add the following override of the Value property, which is the property of the field in the UI. If the end user has changed the value and not yet saved, then the Value property is not necessarily the actual value of the underlying ISBNField (derived from SPFieldText) object or the value of the field in the content database. Note that both the get accessor and set accessor begin by calling [M:System.Web.UI.Control.EnsureChildControls()] (which will call CreateChildControls as needed). Calling EnsureChildControls is mandatory unless (1) you call the base property first and (2) you know that the base property's set and get accessors call [M:System.Web.UI.Control.EnsureChildControls()]. If you were replacing the underlying TextBox child control inherited from TextField with an entirely different type of control, such as a drop-down list box, then the set accessor and get accessor would need to set this control directly rather than call the base property. To ensure that the control initially loads with the value in the underlying ISBNField object, the OnLoad method will set ISBNFieldControl.Value to the value of ItemFieldValue which is the value of the underlying ISBNField object.
public override object Value { get { EnsureChildControls(); return base.Value; } set { EnsureChildControls(); base.Value = (String)value; // The value of the ISBNPrefix field is hardcoded in the // template, so it is not set here. } }
Creating the Field Rendering Template
To create the rendering template
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Add, then New Item.
Select Visual C# Project Items in the Categories window and then Text File in the Templates window.
In the Name box, type ISBNFieldControl.ascx and click Add. (Do not put the file in a subfolder of the project folder or the Post-build commands that you created earlier in this walkthrough will not find it.)
In the ISBNFieldControl.ascx file that is created, add the following markup text:
<%@ Control Language="C#" %> <%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WebControls" %> <SharePoint:RenderingTemplate ID="ISBNFieldControl" > <Template> <asp:Label ID="ISBNPrefix" Text="ISBN" /> <asp:TextBox ID="TextField" /> </Template> </SharePoint:RenderingTemplate>
Note the following facts about this markup:
The ID of the RenderingTemplate must be identical to the string that you used in your override of the DefaultTemplateName property.
The Text attribute of the Label control is set here in the template because it never changes.
An HTML " " element comes between the two controls.
The TextBox definition is identical to the one in the "TextField" RenderingTemplate defined in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES\DefaultTemplates.ascx. But the definition must be repeated here because your override of DefaultTemplateName points to this custom template not the "TextField" template. The same ID is used in the custom template because the base CreateChildControls method (see above) might refer to the control by this ID.
Save the file and close it.
Creating the Field Type Definition
To create the field type definition
In Visual Studio, build the project. The project is not finished, but you need to build at this time to generate a GUID and a Public Key Token for the assembly.
In Solution Explorer, right-click the project name, ISBN_Field_Type, and select Add, then New Item.
Select Visual C# Project Items in the Categories window and then XML File in the Templates window.
In the Name box, type fldtypes_ISBNField.xml and click Add. (The file name must begin with "fldtypes". Also, it must not be put in a subfolder of the project folder or the Post-build commands you created earlier in this walkthrough will not find it.)
In the fldtypes_ISBNField.xml file that is created, add the following markup text:
<?xml version="1.0" encoding="utf-8" ?> <FieldTypes> <FieldType> <Field Name="TypeName">ISBN</Field> <Field Name="ParentType">Text</Field> <Field Name="TypeDisplayName">ISBN</Field> <Field Name="TypeShortDescription">ISBN for a book</Field> <Field Name="UserCreatable">TRUE</Field> <Field Name="ShowOnListCreate">TRUE</Field> <Field Name="ShowOnSurveyCreate">TRUE</Field> <Field Name="ShowOnDocumentLibraryCreate">TRUE</Field> <Field Name="ShowOnColumnTemplateCreate">TRUE</Field> <Field Name="FieldTypeClass">MyCompany.SharePoint.ISBNField, MyCompany.SharePoint.ISBN_Field_Type, Version=1.0.0.0, Culture=neutral, PublicKeyToken=token</Field> </FieldType> </FieldTypes>
This file defines the custom field type for Windows SharePoint Services 3.0. For details about the purpose and meaning of its elements, see Custom Field Type Definition, FieldTypes Element (Field Types), FieldType Element (Field Types), and Field Element (Field Types). Note that the <Field Name="FieldTypeClass"> element must be entirely on one line.
In the <Field Name="FieldTypeClass"> element, replace MyCompany in both the fully qualified class name and the assembly name with the name of your company; and replace token with the Public Key Token for your assembly. To obtain the Public Key Token, select Get Assembly Public Key from the Tools menu. The Public Key Token will appear in the Output window.
Now add the following markup text to the file, just below the last Field element and indented at the same level as the various Field elements:
<RenderPattern Name="DisplayPattern"> <Switch> <Expr> <Column/> </Expr> <Case Value=""> </Case> <Default> <HTML><![CDATA[ISBN ]]></HTML> <Column HTMLEncode="TRUE"/> </Default> </Switch> </RenderPattern>
This RenderPattern element of the DisplayPattern type renders the field on list views and in Display mode. If the field value (represented by the Column element) is an empty string, it performs no function. If the field has a value, the word "ISBN" is rendered, followed by a space and then the value of the field. The column header in list view mode is rendered by a HeaderPattern type of RenderPattern that the custom field type will inherit from its parent "Text" field type. (For more information about render patterns, see RenderPattern Element (Field Types)).
Warning
Do not use the Deploy option on Visual Studio's Build menu, if you are working with version 1.0 of Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions. Doing so will cause a simplified fldtypes*.xml file to be deployed in place of the one you created. The simplified version will not contain the custom RenderPattern template.
Build and Test the Custom Field Type
To build and test the custom field type
Select Rebuild on the Build menu. Because of the Post-build commands you created earlier, this will automatically copy the various files to where they need to be.
Open a Web site in your SharePoint Web Application and create a list called Books.
Add a new column to the list. On the Create Column page, enter "ISBN" as the column name.
Click the radio button for ISBN for a book.
Click the Yes radio button to make the field required.
Leave the Add to default view check box enabled.
Click OK.
Add an item to the list.
On the New Item page, verify that the field is initially set to the default value "0-000-00000-0".
Enter invalid ISBN values to see what kind of errors you get when you try to save the item.
See what happens if you leave the field entirely blank.
Finally, enter 0-262-61108-2 or another value that you know is valid and click OK. (If you get errors for a valid ISBN, be sure there is no blank space at the end of the value.)
Confirm that the value on the list view is preceded by "ISBN" and a space.
Click the item title to open the Display page. Confirm that the field renders in the same way that it renders in list view.
Click Edit Item to edit the field. Confirm that the field is initially set to its current value, not the default.
Change the field to invalid values and confirm that the validation errors appear in Edit mode just as they did in New mode.
How Field Rendering for Mobile Devices Differs from Field Rendering for Computers
In Windows SharePoint Services 3.0, field rendering with custom field rendering controls for mobile devices is similar to field rendering with custom field rendering controls for computers. But keep these differences in mind:
Mobile pages are an entirely different set of pages from the main pages of a Windows SharePoint Services site (which are designed for computer browsers) and they reference a different set of RenderingTemplate objects.
Mobile RenderingTemplate objects are declared in MobileDefaultTemplates.ascx, not DefaultTemplates.ascx.
Mobile field rendering controls have their own namespace, Microsoft.SharePoint.MobileControls and they derive from classes in the ASP.NET System.Web.UI.MobileControls namespace (rather than System.Web.UI.WebControls namespace).
The inheritance hierarchy for mobile field rendering controls is a little different from that of regular field rendering controls. For example, the functions of the TemplateBasedControl and FormComponent in regular field rendering are combined in the SPMobileComponent class.
Custom field rendering controls that you create for mobile contexts rely more on the CreateChildControls method of the control to render a field, and correspondingly less on the rendering template, than is the case for custom field rendering controls that you create for computer browsers. Moreover, when developing custom mobile rendering controls you will not often override the CreateChildControls method itself. Instead, your custom mobile rendering controls will typically override one or more of four methods that are called by the CreateChildControls method:
Next Steps
When your custom field type is thoroughly tested, add a Windows SharePoint Services 3.0 Solution to the Visual Studio project so you can easily deploy it to production servers. For more information, see Solutions Overview.
See Also
Reference
FieldTypes Element (Field Types)
FieldType Element (Field Types)
RenderPattern Element (Field Types)
[M:System.Web.UI.Control.EnsureChildControls()]
Label
System.Text.RegularExpressions
TextBox