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);
}

About these ads
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
    http://pholpar.wordpress.com/2010/01/04/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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: