How to add custom validators to the SharePoint ListFieldIterator

Requirements

Today I had the requirement to add custom validation logic to a SharePoint edit form. The edit form is being displayed using a ListFieldIterator. The requirement was to make sure the Date field could not be set to a date in the past.

The solution (Other solutions are available and are most probably better!) I came up with involved subclassing ListFieldIterator and attaching ASP.NET validators at run-time.

Challenge 1

How do you attach a ASP.NET validation control at run-time.

  • Attach the validator at the correct stage of the page/control life-cycle. In the case of the ListFieldIterator I wanted to attach it after the control had build it’s control tree. Override CreateChildControls, call base, then attach.
  • Find the FormField associated with the SharePoint field (SPField) you want to validate.I have an extension method that I use to parse the control hierarchy of a ListFieldIterator as follows:

Usage:

FormField formField = listFieldIterator.GetFormField("MyInternalFieldName");

public static class ListFieldIteratorExtensions
{
    public static FormField GetFormField(this ListFieldIterator listFieldIterator, string fieldName)
    {
        return GetFormField(listFieldIterator, GetFormFields(listFieldIterator), fieldName);
    }


    public static FormField GetFormField(this ListFieldIterator listFieldIterator, List<FormField> formFields, string fieldName)
    {
        FormField formField = (from form in formFields
                               where form.FieldName.Equals(fieldName, StringComparison.InvariantCultureIgnoreCase)
                               select form).FirstOrDefault();

        if (formField == null)
        {
            throw new GeneralApplicationException("Could not find form field: " + fieldName);
        }

        return formField;
    }

    public static List<FormField> GetFormFields(this ListFieldIterator listFieldIterator)
    {
        if (listFieldIterator == null)
        {
            return null;
        }

        return FindFieldFormControls(listFieldIterator);
    }        

    private static List<FormField> FindFieldFormControls(System.Web.UI.Control root)
    {
        List<FormField> baseFieldControls = new List<FormField>();

        foreach (System.Web.UI.Control control in root.Controls)
        {
            if (control is FormField && control.Visible)
            {
                FormField formField = control as FormField;
                if (formField.Field.FieldValueType == typeof(DateTime))
                {
                    HandleDateField(formField);
                }

                baseFieldControls.Add(formField);
            }
            else
            {
                baseFieldControls.AddRange(FindFieldFormControls(control));
            }
        }

        return baseFieldControls;
    }

    private static void HandleDateField(FormField formField)
    {
        if (formField.ControlMode == SPControlMode.Display)
        {
            return;
        }

        Control dateFieldControl = formField.Controls[0];
        if (dateFieldControl.Controls.Count > 0)
        {
            DateTimeControl dateTimeControl = (DateTimeControl) dateFieldControl.Controls[0].Controls[1];
            TextBox dateTimeTextBox = dateTimeControl.Controls[0] as TextBox;
            if (dateTimeTextBox != null)
            {
                if (!string.IsNullOrEmpty(dateTimeTextBox.Text))
                {
                    formField.Value = DateTime.Parse(dateTimeTextBox.Text, CultureInfo.CurrentCulture);
                }
            } 
        }
    }
}
  • Find the Control that is rendered by the FieldControl.Field.FieldRenderingControl. In my specific case a DateTimeField will render a DateTimeControl. Now that we have the form field we grab the rendering control:

Usage:

Control renderedControl = GetControl(formField);

private static Control GetControl(FieldMetadata formField)
{
    return formField.FindControlRecursive(x => x.GetType() == GetChildControlBasedOnFieldType(formField.Field.FieldRenderingControl));
}

private static Type GetChildControlBasedOnFieldType(object field)
{
    if (field is TextField)
    {
        return typeof(TextBox);
    }

    if (field is DropDownChoiceField)
    {
        return typeof(DropDownList);
    }

    if (field is DateTimeField)
    {
        return typeof (DateTimeControl);
    }

    return null;
}

public static Control FindControlRecursive(this Control control, Func<Control, bool> evaluate)
{
    if (evaluate.Invoke(control))
    {
        return control;
    }

    foreach (Control childControl in control.Controls)
    {
        Control foundControl = FindControlRecursive(childControl, evaluate);
        if (foundControl != null)
        {
            return foundControl;
        }
    }

    return null;
} 

  • Now we have found the control we want to validate, we can add the ASP.NET to it’s parent’s control collection

renderedControl.Parent.Controls.AddAfter(control, validator as Control);

Uses another little extension method:

public static void AddAfter(this ControlCollection collection, Control after, Control control)
{
    int indexFound = -1;
    int currentIndex = 0;
    foreach (Control controlToEvaluate in collection)
    {
        if (controlToEvaluate == after)
        {
            indexFound = currentIndex;
            break;
        }

        currentIndex = currentIndex + 1;
    }

    if (indexFound == -1)
    {
        throw new ArgumentOutOfRangeException("control", "Control not found");
    }

    collection.AddAt(indexFound + 1, control);
}

Tagged ,

3 thoughts on “How to add custom validators to the SharePoint ListFieldIterator

  1. Hector Caban says:

    Love the code!! But if your doing a CompareValidator with 2 datetime fields the GetControl functions returns the correct control but the Ids are the same evertime. This must be a sharepoint thing. Anythoughts on how you would get a CompareValidator to work ?

  2. Peter Holpar says:

    For an alternative way of custom validation have a look at this post:
    Cross field, cross item, cross list or even more complicated validations on SharePoint forms

    Cross field, cross item, cross list or even more complicated validations on SharePoint forms

  3. Nice post! My own FindControlRecursive helped me out many times. Especially, when I deal with MasterPages. For example, one of such usage is shown in my post here – http://dotnetfollower.com/wordpress/2010/12/sharepoint-add-onchange-attribute-to-dropdownchoicefield/.
    Thank you!

Leave a reply to Hector Caban Cancel reply