Walkthrough: Creating a Custom Field Type
Applies to: SharePoint Foundation 2010
This topic provides a step-by-step guide to creating a custom field type. You will create a field that is intended to hold 10-digit International Standard Book Number (ISBN).
For an overview of the steps involved in creating a custom field type and defining it's rendering, see How to: Create a Custom Field Type.
Prerequisites
Microsoft Visual Studio 2010
Setting Up the Project
To set up the custom field project
In Visual Studio, create an Empty SharePoint Project. Make it a farm solution, not a sandboxed solution; and name it ISBN_Field_Type.
Right-click the project name in Solution Explorer and select Properties.
On the Application tab of the Properties dialog, enter Contoso.SharePoint.ISBN_Field_Type as the Assembly name and Contoso.SharePoint as the Default namespace. Leave the Target framework set to .NET Framework 3.5.
If the Solution Platforms box on the Visual Studio Standard Menu does not say "Any CPU" or "x64", open the Build tab and set the Platform Target to either "Any CPU" or "x64". For information about making the choice, see How to: Set the Correct Target Framework and CPU.
Click the Save all files button on the toolbar.
Right-click the project name in Solution Explorer and select Add | New Item.
In the Add New Item dialog box, select Visual C# | Code (or Visual Basic | Code) in the Installed Templates tree.
Select Class in the Templates box, and enter ISBN.Field.cs (or ISBN.Field.vb) in the Name box. Click Add.
Repeat the previous step to create a second class, but enter ISBN.FieldControl.cs (or ISBN.FieldControl.vb) in the Name box. Click Add.
Add a third class the same way and enter ISBN10ValidationRule.cs (or ISBN10ValidationRule.vb) in the Name box. Click Add.
In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.
Use the tree control that opens to map the folder to TEMPLATE\ControlTemplates and click OK.
Right-click the new ControlTemplates folder (not the project name) in Solution Explorer and select Add | New Item.
In the Add New Item dialog box, select SharePoint | 2010 in the Installed Templates tree.
Select a SharePoint User Control in the Templates box, and give the ascx file the name ISBNFieldControl.ascx. Click Add. Visual Studio automatically adds the file to the SharePoint Solution manifest and sets it to be deployed to %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\ControlTemplates. It also at this time adds the assembly to the manifest and sets it to be deployed to the Global Assembly Cache (GAC).
Tip
Do not add the User Control by right-clicking the project name in Solution Explorer. When a User Control is added this way, Visual Studio puts it in a subfolder of TEMPLATE\ControlTemplates and, if it is not moved, Visual Studio will deploy it to a corresponding subfolder of %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\ControlTemplates. Rendering templates in subfolders do not get loaded.
Delete the ISBNFieldControl.ascx.cs and ISBNFieldControl.ascx.designer.cs files (or ISBNFieldControl.ascx.vb and ISBNFieldControl.ascx.designer.vb files) that are created automatically under the ISBNFieldControl.ascx file. They are not needed for this project. The default content of ISBNFieldControl.ascx refers to the ISBNFieldControl.ascx.cs (or ISBNFieldControl.ascx.vb) file you just deleted and, if you build the project at this point, the compiler will give you a warning about the missing file. Ignore the warning: the default content is changed in a step later in this topic.
In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.
Use the tree control that opens to map the folder to TEMPLATE\XML and click OK.
Right-click the new XML folder (not the project name) in Solution Explorer and select Add | New Item.
In the Add New Item dialog box, select Visual C# | Data (or Visual Basic | Data) and then XML File in the Templates window.
In the Name box, type fldtypes_ISBNField.xml and click Add.
In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.
Use the tree control that opens to map the folder to TEMPLATE\LAYOUTS\XSL and click OK.
Right-click the new XSL folder (not the project name) in Solution Explorer and select Add | New Item.
In the Add New Item dialog box, select Visual C# | Data (or Visual Basic | Data) and then XSLT File in the Templates window.
In the Name box, type fldtypes_ISBNField.xsl and click Add. Note this is very similar to the name of the previous file you created. The two files have different purposes and are deployed to different folders. Keep them distinct as you work through this topic.
Right-click the References node in Solution Explorer, click Add Reference, and select PresentationFramework.dll on the .NET tab in the Add Reference dialog box. Click OK. (This assembly contains the definition of the ValidationRule class that you create in the next procedure.)
Creating the Validation Rule Class
To create a validation rule class
Open the ISBN10ValidationRule.cs (or ISBN10ValidationRule.vb) file and add the following statements.
using System.Text.RegularExpressions; using System.Windows.Controls; using System.Globalization;
Imports System.Text.RegularExpressions Imports System.Windows.Controls Imports System.Globalization
Change the namespace to conform to the guidelines in Namespace Naming Guidelines. In this walkthrough, use Contoso.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
Public Class ISBN10ValidationRule Inherits ValidationRule Private Const ISBNMODULO As Int32 = 11 Public Overrides Function Validate(ByVal value As Object, ByVal cultureInfo As CultureInfo) As ValidationResult Dim iSBN As String = CType(value, String) Dim errorMessage As String = "" Dim rxISBN As New Regex("^(?'GroupID'\d{1,5})-(?'PubPrefix'\d{1,7})-(?'TitleID'\d{1,6})-(?'CheckDigit'[0-9X]{1})$") If Not rxISBN.IsMatch(iSBN) Then errorMessage = "An ISBN must have this structure:" & vbLf & "1-5 digit Group ID, hyphen, " & vbLf & "1-7 digit Publisher Prefix, hyphen, " & vbLf & "1-6 digit Title ID, hyphen, " & vbLf & "1 Check Digit (which can be ""X"" to indicate ""10"")." & vbLf End If If errorMessage = "" Then ' Matched the RegEx, so check for group length errors. Dim mISBN As Match = rxISBN.Match(iSBN) Dim groupsInString As GroupCollection = mISBN.Groups Dim groupID As String = groupsInString("GroupID").Value Dim pubPrefix As String = groupsInString("PubPrefix").Value If (groupID.Length + pubPrefix.Length) >= 9 Then errorMessage = "The Group ID and Publisher Prefix can total no more than 8 digits." & vbLf End If Dim titleID As String = groupsInString("TitleID").Value If ((groupID.Length + pubPrefix.Length) + titleID.Length) <> 9 Then errorMessage = errorMessage & "The Group ID, Publisher Prefix, and " & vbLf & "Title ID must total exactly 9 digits." & vbLf End If If errorMessage = "" Then 'No group length errors, so verify the check digit algorithm. Dim checkDigitValue As Int32 Dim checkDigit As String = groupsInString("CheckDigit").Value ' To ensure check digit is one digit, "10" is represented by "X". If checkDigit = "X" Then checkDigitValue = 10 Else checkDigitValue = Convert.ToInt32(checkDigit) End If Dim iSBN1st3Groups As String = groupID & pubPrefix & titleID 'Concatenate without the hyphens. ' Sum the weighted digits. Dim weightedSum As Int32 = (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 Dim remainder As Int32 = weightedSum Mod ISBNMODULO ' ISBN is invalid if weighted sum modulo 11 is not 0. If remainder <> 0 Then errorMessage = "Number fails Check Digit verification." End If If errorMessage = "" Then ' 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 If ' end no group length errors Else ' There was some error in a group length Return New ValidationResult(False, errorMessage) End If ' end RegEx match succeeded Else ' There was a RegEx match failure Return New ValidationResult(False, errorMessage) End If End Function ' end Validate method End Class ' 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 (or ISBN.Field.vb) file.
Add the following statements.
using Microsoft.SharePoint; using Microsoft.SharePoint.WebControls; using Microsoft.SharePoint.Security; using System.Windows.Controls; using System.Globalization; using System.Runtime.InteropServices; using System.Security.Permissions;
Imports Microsoft.SharePoint Imports Microsoft.SharePoint.WebControls Imports Microsoft.SharePoint.Security Imports System.Windows.Controls Imports System.Globalization Imports System.Runtime.InteropServices Imports System.Security.Permissions
Add the following statements. These enable your class implementation to reference other classes you create in later steps. Until you create those classes you may see compiler warnings about these statements.
using Contoso.SharePoint.WebControls; using Contoso.System.Windows.Controls;
Imports Contoso.SharePoint.WebControls Imports Contoso.System.Windows.Controls
Ensure that the namespace is Contoso.SharePoint.
Be sure that the class is named ISBNField and change its declaration to specify that it inherits from SPFieldText.
public class ISBNField : SPFieldText { }
Public Class ISBNField Inherits SPFieldText End Class
Add the following required constructors for the class.
public ISBNField(SPFieldCollection fields, string fieldName) : base(fields, fieldName) { } public ISBNField(SPFieldCollection fields, string typeName, string displayName) : base(fields, typeName, displayName) { }
Public Sub New(fields as SPFieldCollection, fieldname as String) MyBase.New(fields, fieldName) End Sub Public Sub New(fields as SPFieldCollection, typeName as String, displayName as String) MyBase.New(fields, typeName, displayName) End Sub
Add the following override of FieldRenderingControl to the class. ISBNFieldControl is a class that you create in a later step.
public override BaseFieldControl FieldRenderingControl { [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)] get { BaseFieldControl fieldControl = new ISBNFieldControl(); fieldControl.FieldName = this.InternalName; return fieldControl; } }
Public Overrides ReadOnly Property FieldRenderingControl() As BaseFieldControl Get Dim fieldControl As BaseFieldControl = New ISBNFieldControl() fieldControl.FieldName = Me.InternalName Return fieldControl End Get End Property
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
Public Overrides Function GetValidatedString(ByVal value As Object) As String If (Me.Required = True) AndAlso ((value Is Nothing) OrElse (CType(value, String) = "")) Then Throw New SPFieldValidationException(Me.Title & " must have a value.") Else Dim rule As New ISBN10ValidationRule() Dim result As ValidationResult = rule.Validate(value, cultureInfo.InvariantCulture) If Not result.IsValid Then Throw New SPFieldValidationException(CType(result.ErrorContent, String)) Else Return MyBase.GetValidatedString(value) End If End If End Function ' 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 the ISBN.FieldControl.cs (or ISBN.FieldControl.vb) file.
Add the following statements.
using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Runtime.InteropServices; using Microsoft.SharePoint; using Microsoft.SharePoint.WebControls;
Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Runtime.InteropServices Imports Microsoft.SharePoint Imports Microsoft.SharePoint.WebControls
Change the namespace to Contoso.SharePoint.WebControls.
Be sure that the class is named ISBNFieldControl and change its declaration to specify that it inherits from TextField.
public class ISBNFieldControl : TextField { }
Public Class ISBNFieldControl Inherits TextField End Class
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;
Protected Label ISBNPrefix
Add another protected field for an ASP.NET Label Web control that will render the current value of the field in Display mode.
protected Label ISBNValueForDisplay;
Protected Label ISBNValueForDisplay
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 that you will add, in a later step, to the .ascx file that you created earlier. (When your project is finished, that file is deployed to to the folder %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\CONTROLTEMPLATES.) If none of ControlTemplate, Template, or TemplateName, are overridden, 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 { if (this.ControlMode == SPControlMode.Display) { return this.DisplayTemplateName; } else { return "ISBNFieldControl"; } } }
Protected Overrides ReadOnly Property DefaultTemplateName() As String Get If Me.ControlMode = SPControlMode.Display Then Return Me.DisplayTemplateName Else Return "ISBNFieldControl" End Get End Property
Add the following override of DisplayTemplateName. The String that you are assigning to this property is the ID of a RenderingTemplate object that you will add, in a later step to the ascx file that you created earlier. This object renders the field's value in Display mode.
public override string DisplayTemplateName { get { return "ISBNFieldControlForDisplay"; } set { base.DisplayTemplateName = value; } }
Public Overrides Property DisplayTemplateName() As String Get Return "ISBNFieldControlForDisplay" End Get Set MyBase.DisplayTemplateName = Value End Set End Property
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 set accessor for the ISBNField's FieldRenderingControl property — see the override of FieldRenderingControl in ISBN.Field.cs [or ISBN.Field.vb].)
protected override void CreateChildControls() { if (this.Field != null) { }// end if there is a non-null underlying ISBNField // Do nothing if the ISBNField is null. }
Protected Overrides Sub CreateChildControls() If Me.Field IsNot Nothing Then End If ' end if there is a non-null underlying ISBNField ' Do nothing if the ISBNField is null or control mode is Display. End Sub
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 %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\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();
' Make sure inherited child controls are completely rendered. MyBase.CreateChildControls()
Add the following lines to associate the child controls in the rendering templates with the child control fields declared in your custom field control (or inherited from its parent). You must do this here because the call to the base CreateChildControls will associate the inherited child controls to the rendering templates used by the parent of your custom field class, not to the custom rendering templates, 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"); this.ISBNValueForDisplay = (Label)TemplateContainer.FindControl("ISBNValueForDisplay");
' Associate child controls in the .ascx file with the ' fields allocated by this control. Me.ISBNPrefix = CType(TemplateContainer.FindControl("ISBNPrefix"), Label) Me.textBox = CType(TemplateContainer.FindControl("TextField"), TextBox) Me.ISBNValueForDisplay = CType(TemplateContainer.FindControl("ISBNValueForDisplay"), Label)
Add the following structure below the control association code. Your field uses a different rendering template (that you create in a later step of this topic) in Display mode from the one it uses in New and Edit modes; hence, different child controls are initialized depending on the mode.
if (this.ControlMode != SPControlMode.Display) { } else // control mode is Display { }// end control mode is Display
If Not Me.ControlMode = SPControlMode.Display Then Else ' control mode is display End If ' end control mode is Display
Add the following inner conditional structure inside the "if" (or "If") clause of the conditional structure you made in the previous step. 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.
If Not Me.Page.IsPostBack Then End If ' 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
If Me.ControlMode = SPControlMode.New Then textBox.Text = "0-000-00000-0" End If ' end assign default value in New mode
In the else (or Else) block that runs when the control mode is Display, add the following code to initialize the field to its current value from the content database.
// Assign current value from database to the label control ISBNValueForDisplay.Text = (String)this.ItemFieldValue;
' Assign current value from database to the label control ISBNValueForDisplay.Text = CType(Me.ItemFieldValue, String)
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) { // 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"); this.ISBNValueForDisplay = (Label)TemplateContainer.FindControl("ISBNValueForDisplay"); if (this.ControlMode != SPControlMode.Display) { 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 control mode is not Display else // control mode is Display { // Assign current value from database to the label control ISBNValueForDisplay.Text = (String)this.ItemFieldValue; }// end control mode is Display }// end if there is a non-null underlying ISBNField // Do nothing if the ISBNField is null. }
Protected Overrides Sub CreateChildControls() If Me.Field IsNot Then ' Make sure inherited child controls are completely rendered. MyBase.CreateChildControls() ' Associate child controls in the .ascx file with the ' fields allocated by this control. Me.ISBNPrefix = CType(TemplateContainer.FindControl("ISBNPrefix"), Label) Me.textBox = CType(TemplateContainer.FindControl("TextField"), TextBox) Me.ISBNValueForDisplay = CType(TemplateContainer.FindControl("ISBNValueForDisplay"), Label) If Not Me.ControlMode = SPControlMode.Display Then If Not Me.Page.IsPostBack Then If Me.ControlMode = SPControlMode.New Then textBox.Text = "0-000-00000-0" End If ' end assign default value in New mode End If ' end if this is not a postback 'Do not reinitialize on a postback. Else ' control mode is display ' Assign current value from database to the label control ISBNValueForDisplay.Text = CType(Me.ItemFieldValue, String) End If ' end control mode is Display End If ' end if there is a non-null underlying ISBNField ' Do nothing if the ISBNField is null or control mode is Display. End Sub
Add the following override of the Value property, which is the value 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 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 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 of your override of the Value property would need to set this control directly rather than call a base property. To ensure that the control initially loads with the value in the underlying ISBNField object, the OnLoad() method sets 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. } }
Public Overrides Property Value() As Object Get EnsureChildControls() Return MyBase.Value End Get Set(ByVal value As Object) EnsureChildControls() MyBase.Value = CType(value, String) ' The value of the ISBNPrefix field is hardcoded in the ' template, so it is not set here. End Set End Property
Creating the Field Rendering Template
To create the rendering templates
Open the ISBNFieldControl.ascx file.
The following directives are already in the file.
<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %> <%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="asp" Namespace="System.Web.UI" Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %> <%@ Import Namespace="Microsoft.SharePoint" %> <%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
Below this markup is a <%@ Control directive that makes reference to files that you deleted in an earlier step and contains other attributes that are not used in this project. Replace it with the following simplified directive.
<%@ Control Language="C#" %>
Below the directives, add the following markup.
<SharePoint:RenderingTemplate ID="ISBNFieldControl" runat="server"> <Template> <asp:Label ID="ISBNPrefix" Text="ISBN" runat="server" /> <asp:TextBox ID="TextField" runat="server" /> </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 %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\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.
Add the following additional RenderingTemplate just below the first one.
<SharePoint:RenderingTemplate ID="ISBNFieldControlForDisplay" runat="server"> <Template> <asp:Label ID="ISBNValueForDisplay" runat="server" /> </Template> </SharePoint:RenderingTemplate>
This RenderingTemplate will be designated the default template in Display mode by the CreateChildControls method.
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.
Open the fldtypes_ISBNField.xml file and replace its contents with the following markup.
<?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">Contoso.SharePoint.ISBNField, $SharePoint.Project.AssemblyFullName$</Field> </FieldType> </FieldTypes>
This file defines the custom field type for SharePoint Foundation. For details about the purpose and meaning of its elements, see How to: Create a Custom Field Type Definition, Understanding the FldTypes.xml File, 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.
The value of the <Field Name="FieldTypeClass"> element is the fully qualified name of your custom field class followed by a comma and then a Visual Studio 2010 token ($SharePoint.Project.AssemblyFullName$). When you compile the project, a copy of this file is created in which the token is replaced by the full four-part name of the assembly. It is that copy which is deployed when you select Deploy Solution from the Visual Studio Build menu of Visual Studio 2010. If you are not using Visual Studio 2010, you will need to compile the project at this time, even though it is not finished, in order to generate a Public Key Token. You can then use the tool described at How to: Create a Tool to Get the Full Name of an Assembly to obtain the full four-part name and paste it in manually in place of the token.
Creating the XSLT Stylesheet
To create the XSLT stylesheet
Open the fldtypes_ISBNField.xsl file and replace everything below the <?xml version="1.0" encoding="utf-8"?> tag with the following markup.
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="https://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="https://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="https://schemas.microsoft.com/ASPNET/20" xmlns:__designer="https://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal"> <xsl:template match="FieldRef[@Name = 'ISBN']" mode="Text_body"> <xsl:param name="thisNode" select="." /> <span style="background-color:lightgreen;font-weight:bold"> <xsl:value-of select="$thisNode/@*[name()=current()/@Name]" /> </span> </xsl:template > </xsl:stylesheet>
The XSLT stylesheet renders the field on list views. The cells in the ISBN column of list views have a light green background with the ISBN value in bold.
The column header in list view mode is rendered by another XSLT stylesheet that is supplied in the built-in file fldtypes.xsl in %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATES\LAYOUTS\XSL.
Build and Test the Custom Field Type
To build and test the custom field type
Select Deploy on the Build menu. This automatically rebuilds the assembly, deploys the assembly to the GAC, deploys the ascx file to %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\ControlTemplates, deploys the fldtypes*.xml file to %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\XML, deploys the fldtypes*.xsl file to %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\TEMPLATE\LAYOUTS\XSL, and recycles the Web application.
Note
If your development environment is a multi-server farm, then the deployment step will not occur automatically. Deploy the solution, isbn_field_type.wsp, from the Solutions Management page of the Central Administration application or with a SharePoint Management Shell cmdlet.
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" and that, in addition to the field title "ISBN", the value itself also is immediately preceded by "ISBN" as defined in the rendering template for the New and Edit modes.
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 Save. (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 bold against a light green background.
Click the item title to open the Display page. Confirm that the field renders with its current value in the content database. Note that although the field title "ISBN" is present, the value itself is not immediately preceded by "ISBN" as it is in the Edit and New modes because this prefix was not part of the rendering template for the Display mode.
Click Edit Item to edit the field. Confirm that the field is initially set to its current value, not the default, and that in addition to the field title "ISBN", the value itself also is immediately preceded by "ISBN" as defined in the rendering template for the New and Edit modes.
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 SharePoint Foundation, 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 SharePoint Foundation site (which are designed for computer browsers) and they reference a different set of RenderingTemplate objects.
Mobile RenderingTemplate objects are declared in MobileDefaultTemplates.ascx and GbwMobileDefaultTemplates.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:
See Also
Reference
FieldTypes Element (Field Types)
FieldType Element (Field Types)
RenderPattern Element (Field Types)
Label
OnLoad()
RegularExpressions
TextBox
Concepts
How to: Create a Custom Field Class
How to: Create a Custom Field Type Definition
How to: Create a Field Rendering Control
How to: Create Field Rendering Templates