Wesley Bakker

Interesting things I encounter doing my job...

Sponsors

News

Wesley Bakker
motion10
Rivium Quadrant 151
2909 LC Capelle aan den IJssel
Region of Rotterdam
The Netherlands
Phone: +31 10 2351035

(feel free to chat with me)
 

Add to Technorati Favorites

Regular Expression Validated Custom SPField

Ever wandered about how it would be if you could validate the input of your clients with some regular expressions? Roaming the internet searching for a solution you do find some guys and girls who write about the fact that they created a Regular Expression field for SharePoint  but they don’t explain how they did it. In this post I’ll explain to you how to create such a field. Which isn’t to difficult.

Think first, code later.

This is something that bothers me for a long time. SharePoint developers seem to be the copy and paste masters of this universe. They forget design principles, act like robots doing what they’ve been told and forget to be creative.

When reading examples on how to create custom fields I’m having trouble not to cry. Everybody starts with a Field, a FieldControl and an UserControl. No matter what kind of field, that’s how it should be done. Next they copy the files to the correct location and they have a new field. Perfect, right? WRONG! PLAIN WRONG!

Each developer knows that if he’s about to create a class, the first thing he should do is to check whether there might be an existing class that implements most of the features already. If so, we inherit that class and create some extra methods and or properties. In our case we are going validate a string input by matching it with a regular expression. And SharePoint does have a great solution for string input already. It’s called SPFieldText.

Get started with the SPFieldRegexMatch

So we need a class, that inherits from the SPFieldText and implements two extra properties: ValidationExpression and ErrorMessage:

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
 
namespace Motion10.SharePoint2007 {
    /// <summary>
    /// Field that validates it value by the given regular expression
    /// </summary>
    [Guid("92A8DAFE-92C5-407c-A1E6-7BF0C80FB904")]
    public class SPFieldRegexMatch : SPFieldText {
        /// <summary>
        /// Initializes a new instance of the <see cref="SPFieldRegexMatch"/> class.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <param name="fieldName">Name of the field.</param>
        public SPFieldRegexMatch(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName) {
            base.MaxLength = this.MaxLength;
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="SPFieldRegexMatch"/> class.
        /// </summary>
        /// <param name="fields">An <see cref="T:Microsoft.SharePoint.SPFieldCollection"></see> object that represents the field collection.</param>
        /// <param name="typeName">A string that contains the name of the field type, which can be a string representation of an <see cref="T:Microsoft.SharePoint.SPFieldType"></see> value.</param>
        /// <param name="displayName">A string that contains the display name of the field.</param>
        public SPFieldRegexMatch(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName) {
            base.MaxLength = this.MaxLength;
        }
 
        /// <summary>
        /// Gets or sets the maximum number of characters that can be typed in the field.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A 32-bit integer that specifies the maximum number of characters.
        /// </returns>
        new public virtual int MaxLength {
            get {
                string propVal = this.GetCustomProperty("MaxLength") + "";
 
                int retVal;
                if(int.TryParse(propVal, out retVal)){
                    return retVal;
                }
 
                return 0xff; 
            }
            set {
                this.SetCustomProperty("MaxLength", value);
                base.MaxLength = value;
            }
        }
 
        /// <summary>
        /// Gets or sets the validation expression.
        /// </summary>
        /// <value>The validation expression.</value>
        public virtual string ValidationExpression {
            get { return this.GetCustomProperty("ValidationExpression") + ""; }
            set { this.SetCustomProperty("ValidationExpression", value); }
        }
 
        /// <summary>
        /// Gets or sets the error message.
        /// </summary>
        /// <value>The error message.</value>
        public virtual string ErrorMessage {
            get {
                string retVal = this.GetCustomProperty("ErrorMessage") + ""; ;
                if (string.IsNullOrEmpty(retVal)) {
                    retVal = string.Concat(this.Title,
                                           " does not match the regular expression: ",
                                           HttpUtility.HtmlEncode(this.ValidationExpression));
                }
 
                return retVal;
            }
            set { this.SetCustomProperty("ErrorMessage", value); }
        }
 
        /// <summary>
        /// Used for data serialization logic and for field validation logic that is specific to a custom field type to convert the field value object into a validated, serialized string.
        /// </summary>
        /// <param name="value">An object that represents the value object to convert.</param>
        /// <returns>
        /// A string that serializes the value object.
        /// </returns>
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
        public override string GetValidatedString(object value) {
            string retVal = base.GetValidatedString(value);
            string textValue = base.GetFieldValueAsText(value);
 
            if (!string.IsNullOrEmpty(textValue) && !string.IsNullOrEmpty(this.ValidationExpression)) {
                Regex validationRegex = null;
                try {
                    validationRegex = new Regex(this.ValidationExpression);
                }
                catch (ArgumentException) {
                    throw new SPFieldValidationException("The configured regular expression is not valid. Please contact an administrator of this list to correct the issue.");
                }
 
                if (!validationRegex.IsMatch(textValue)) {
                    throw new SPFieldValidationException(this.ErrorMessage);
                }
            }
 
            return retVal;
        }
    }
}

As you can read in this code sample it’s all pretty straight forward. We use the GetCustomProperty and SetCustomProperty to save the SPFieldRegexMatch its properties and we override the GetValidatedString method to indeed validate our input.

Unfortunately SharePoint does NOT call the property its setter method so we can not validate the regular expression on input. Which is the reason why we validate the regular expression on validation.

We do have our custom field type but, how do we signal SharePoint that we have this lovely new field?

Continue with some CAML

SharePoint has its fields defined in xml files in de template/xml folder which names start with ‘fldtypes_’. All we need to do is create an xml file named 'fldtypes_motion10.xml' and define our custom field like so:

<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
  <FieldType>
    <Field Name="TypeName">RegexMatch</Field>
    <Field Name="ParentType">Text</Field>
    <Field Name="TypeDisplayName">Single line of validated text</Field>
    <Field Name="TypeShortDescription">Single line of validated text(regular expression validation)</Field>
    <Field Name="UserCreatable">TRUE</Field>
    <Field Name="Sortable">TRUE</Field>
    <Field Name="AllowBaseTypeRendering">TRUE</Field>
    <Field Name="Filterable">TRUE</Field>
    <Field Name="FieldTypeClass">Motion10.SharePoint2007.SPFieldRegexMatch, SharePointSolutionPack, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a7cd02bdf107f7a</Field>
    <RenderPattern Name="DisplayPattern" Type="Text">
      <HTML><![CDATA[<span title="Regular Expression Match field by motion10">]]></HTML>
      <Column HTMLEncode="TRUE" />
      <HTML><![CDATA[</span>]]></HTML>
    </RenderPattern>
    <PropertySchema>
      <Fields>
        <Field Name="MaxLength"
               DisplayName="Max Length:"
               Required="TRUE"
               MaxLength="3"
               Min="1"
               Max="255"
               DisplaySize="3"
               Type="Integer">
          <Default>255</Default>
        </Field>
        <Field Name="ValidationExpression"
               DisplayName="Validation Expression:"
               Required="TRUE"
               MaxLength="500"
               DisplaySize="35"
               Type="Text">
          <Default></Default>
        </Field>
        <Field Name="ErrorMessage"
               DisplayName="Error Message:"
               MaxLength="500"
               DisplaySize="35"
               Type="Text">
          <Default></Default>
        </Field>
      </Fields>
    </PropertySchema>
  </FieldType>
</FieldTypes>

This xml is very straight forward as well. Just for fun I inserted a RenderPattern but that’s not necessary at all. Since we inherit from SPFieldText, we have our rendering templates already.

With the PropertySchema we define the editable properties for which the UI will be rendered automagically. So all that’s left to do is to create a new WSPBuilder solution, add the class and the xml file, built, deploy and we are finished!

Cheers and have fun,

Wesley

P.S. the ValidationExpression and ErrorMessage properties are declared virtual with a reason. It’s very simple to override those properties and create a new special field. Like SPFieldEmailAddress for example:

using System;
using System.Runtime.InteropServices;
using Microsoft.SharePoint;
 
namespace Motion10.SharePoint2007 {
    /// <summary>
    /// Field that validates its value to check whether it is an email address
    /// </summary>
    [Guid("D306F9F2-2CF2-4ff3-BA4D-CC1C51126DCC")]
    public sealed class SPFieldEmailAddress : SPFieldRegexMatch {
        /// <summary>
        /// Initializes a new instance of the <see cref="SPFieldEmailAddress"/> class.
        /// </summary>
        /// <param name="fields">The fields.</param>
        /// <param name="fieldName">Name of the field.</param>
        public SPFieldEmailAddress(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName) {
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="SPFieldEmailAddress"/> class.
        /// </summary>
        /// <param name="fields">An <see cref="T:Microsoft.SharePoint.SPFieldCollection"></see> object that represents the field collection.</param>
        /// <param name="typeName">A string that contains the name of the field type, which can be a string representation of an <see cref="T:Microsoft.SharePoint.SPFieldType"></see> value.</param>
        /// <param name="displayName">A string that contains the display name of the field.</param>
        public SPFieldEmailAddress(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName) {
        }
 
        /// <summary>
        /// Gets or sets the maximum number of characters that can be typed in the field.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A 32-bit integer that specifies the maximum number of characters.
        /// </returns>
        /// <remarks>The setter is not implemented.</remarks>
        public override int MaxLength {
            get {
                return 255;
            }
            set {
                throw new NotImplementedException();
            }
        }
 
        /// <summary>
        /// Gets or sets the validation expression.
        /// </summary>
        /// <remarks>The setter is not implemented.</remarks>
        public override string ValidationExpression {
            get {
                return @"^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$";
            }
            set {
                throw new NotImplementedException();
            }
        }
 
        /// <summary>
        /// Gets or sets the error message.
        /// </summary>
        /// <value>The error message.</value>
        /// <remarks>The setter is not implemented.</remarks>
        public override string ErrorMessage {
            get {
                string retVal = string.Concat(this.Title,
                                           " does not contain a valid email address.");
 
                return retVal;
            }
            set {
                throw new NotImplementedException();
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
  <FieldType>
    <Field Name="TypeName">EmailAddress</Field>
    <Field Name="ParentType">Text</Field>
    <Field Name="TypeDisplayName">Email address</Field>
    <Field Name="TypeShortDescription">Email address</Field>
    <Field Name="UserCreatable">TRUE</Field>
    <Field Name="Sortable">TRUE</Field>
    <Field Name="AllowBaseTypeRendering">TRUE</Field>
    <Field Name="Filterable">TRUE</Field>
    <Field Name="FieldTypeClass">Motion10.SharePoint2007.SPFieldEmailAddress, SharePointSolutionPack, Version=1.0.0.0, Culture=neutral, PublicKeyToken=4a7cd02bdf107f7a</Field>
    <RenderPattern Name="DisplayPattern" Type="Text">
      <HTML><![CDATA[<span title="Email address field by motion10">]]></HTML>
      <Column HTMLEncode="TRUE" />
      <HTML><![CDATA[</span>]]></HTML>
    </RenderPattern>
  </FieldType>
</FieldTypes>
Posted: Feb 23 2009, 02:31 PM by webbes | with 2 comment(s)
Filed under:

Comments

No Comments