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

  1. In Visual Studio, create an Empty SharePoint Project. Make it a farm solution, not a sandboxed solution; and name it ISBN_Field_Type.

  2. Right-click the project name in Solution Explorer and select Properties.

  3. 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.

  4. 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.

  5. Click the Save all files button on the toolbar.

  6. Right-click the project name in Solution Explorer and select Add | New Item.

  7. In the Add New Item dialog box, select Visual C# | Code (or Visual Basic | Code) in the Installed Templates tree.

  8. Select Class in the Templates box, and enter ISBN.Field.cs (or ISBN.Field.vb) in the Name box. Click Add.

  9. Repeat the previous step to create a second class, but enter ISBN.FieldControl.cs (or ISBN.FieldControl.vb) in the Name box. Click Add.

  10. Add a third class the same way and enter ISBN10ValidationRule.cs (or ISBN10ValidationRule.vb) in the Name box. Click Add.

  11. In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.

  12. Use the tree control that opens to map the folder to TEMPLATE\ControlTemplates and click OK.

  13. Right-click the new ControlTemplates folder (not the project name) in Solution Explorer and select Add | New Item.

  14. In the Add New Item dialog box, select SharePoint | 2010 in the Installed Templates tree.

  15. 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.

  16. 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.

  17. In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.

  18. Use the tree control that opens to map the folder to TEMPLATE\XML and click OK.

  19. Right-click the new XML folder (not the project name) in Solution Explorer and select Add | New Item.

  20. In the Add New Item dialog box, select Visual C# | Data (or Visual Basic | Data) and then XML File in the Templates window.

  21. In the Name box, type fldtypes_ISBNField.xml and click Add.

  22. In Solution Explorer, right-click the project name and select Add, then SharePoint Mapped Folder.

  23. Use the tree control that opens to map the folder to TEMPLATE\LAYOUTS\XSL and click OK.

  24. Right-click the new XSL folder (not the project name) in Solution Explorer and select Add | New Item.

  25. In the Add New Item dialog box, select Visual C# | Data (or Visual Basic | Data) and then XSLT File in the Templates window.

  26. 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.

  27. 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

  1. 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
    
  2. Change the namespace to conform to the guidelines in Namespace Naming Guidelines. In this walkthrough, use Contoso.System.Windows.Controls.

  3. 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

  1. Open the ISBN.Field.cs (or ISBN.Field.vb) file.

  2. 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
    
  3. 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
    
  4. Ensure that the namespace is Contoso.SharePoint.

  5. 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
    
  6. 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
    
  7. 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
    
  8. 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.

  9. Save and close the file.

Creating the Field Rendering Control

To create the field rendering control

  1. Open the ISBN.FieldControl.cs (or ISBN.FieldControl.vb) file.

  2. 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
    
  3. Change the namespace to Contoso.SharePoint.WebControls.

  4. 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
    
  5. 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
    
  6. 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
    
  7. 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
    
  8. 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
    
  9. 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
    
  10. 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()
    
  11. 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)
    
  12. 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
    
  13. 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.
    
  14. 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
    
  15. 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)
    
  16. 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
    
  17. 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

  1. Open the ISBNFieldControl.ascx file.

  2. 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" %>
    
  3. 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#" %>
    
  4. Below the directives, add the following markup.

    <SharePoint:RenderingTemplate ID="ISBNFieldControl" runat="server">
      <Template>
        <asp:Label ID="ISBNPrefix" Text="ISBN" runat="server" />
        &nbsp;
        <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 "&nbsp;" 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.

  5. 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

  1. 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.

  2. 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.

  3. 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

  1. 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.

  2. Open a Web site in your SharePoint Web Application and create a list called Books.

  3. Add a new column to the list. On the Create Column page, enter "ISBN" as the column name.

  4. Click the radio button for ISBN for a book.

  5. Click the Yes radio button to make the field required.

  6. Leave the Add to default view check box enabled.

  7. Click OK.

  8. Add an item to the list.

  9. 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.

  10. Enter invalid ISBN values to see what kind of errors you get when you try to save the item.

  11. See what happens if you leave the field entirely blank.

  12. 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.)

  13. Confirm that the value on the list view is bold against a light green background.

  14. 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.

  15. 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.

  16. 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)

Field Element (Field Types)

Namespace Naming Guidelines

RenderPattern Element (Field Types)

AlternateTemplateName

BaseValidator

ControlTemplate

CreateChildControls

DefaultAlternateTemplateName

DefaultTemplateName

EnsureChildControls

FieldRenderingControl

GetValidatedString

ItemFieldValue

Label

OnLoad()

RegularExpressions

RenderingTemplate

SPControlMode

SPFieldText

SPFieldValidationException

Template

TemplateName

TextBox

TextField

ValidationRule

Value

Concepts

How to: Create a Custom Field Class

How to: Create a Custom Field Type Definition

Custom Field Types Deployment

Custom Field Data Validation

How to: Create a Field Rendering Control

How to: Create Field Rendering Templates

How to: Create a Custom Field Type

Solutions Overview