ASP.NET MVC 2 client side validation for dynamic fields added with ajax

It’s quite common to add fields to a form dynamically using ajax, for example you may have a list of phone numbers for a user, and they could have many different phone numbers.

Using MVC you can easily add fields to a form by making an ajax call to an action that returns the rendered result of a partial view, but if you’ve tried doing this when using client side validation you’ll find the client side validation doesn’t work for those fields dynamically added to the form via the ajax call.

In this article I’ll explain a simple way to enable client side validation on those dynamically added fields.

Download Source

Lets start by building a simple application that uses the phone number example. I have a person model that looks like this:

public class Person
{
    public Person()
    {
        PhoneNumbers = new List<PhoneNumber>()
                            {
                                new PhoneNumber()
                            };
    }

    [Required]
    public string Forename { get; set; }

    [Required]
    public string Surname { get; set; }

    public IList<PhoneNumber> PhoneNumbers { get; set; }
}

My PhoneNumer model looks like this:

public class PhoneNumber
{
    [Required]
    public PhoneType Type { get; set; }

    [Required]
    public string Number { get; set; }
}

And I have a simple enumeration for the phone type:

public enum PhoneType
{
    Home,
    Work,
    Mobile
}

Now I need a view to edit this information that looks like this:

<script type="text/javascript">
    $().ready(function () {
        $("#add-phone").click(function () {
            $.ajax({
                url: '<%: Url.Action("GetNewPhone") %>',
                success: function (data) {
                    $(".phone-numbers").append(data);
                }
            });
        });
    });
</script>

<% Html.EnableClientValidation(); %>

<% using (Html.BeginForm())
    {%>

    <%: Html.EditorForModel() %>

    <fieldset>
        <legend>Phone Numbers</legend>
        <div class="phone-numbers">
            <%: Html.EditorFor(m => m.PhoneNumbers) %>
        </div>
        <div style="padding: 10px 0px 10px 0px">
            <a id="add-phone" href="javascript:void(0);">Add another</a>
        </div>
    </fieldset>

    <div>
        <input type="submit" value="Save" />
    </div>

<%
    }%>

The above view is using an editor template for the phone number that looks like this:

<%
    using (Html.BeginCollectionItem("PhoneNumbers"))
    { %>
    <div style="padding: 5px 0px 5px 0px">
        <%: Html.LabelFor(m => m.Type) %>
        <%: Html.EditorFor(m => m.Type) %>
        <%: Html.LabelFor(m => m.Number) %>
        <%: Html.EditorFor(m => m.Number) %>
        <%: Html.ValidationMessageFor(m => m.Number) %>
    </div>
<%
    }
%>

Here you can see I’m using the BeginCollectionItem extension method written by Steve Sanderson which I extended in this post to support nested collections. It essentially creates a hidden index field using a GUID, then sets the HtmlPrefix to use that GUID, where the GUIDs are resused after a post making binding dynamic lists of objects nice and easy.

I also have an editor template for phone type to generate a drop down  using the enumeration values:

<%: Html.DropDownListFor(m => m, new SelectList(Enum.GetNames(typeof(DynamicFormValidation.Models.PhoneType)))) %>

Okay so that’s about everything. When I run the application I can add multiple phone number lines with ajax, but you’ll notice the client side validation doesn’t work for the ones created dynamically.

If I fill in the other fields then the dyanamically added field does get validated on the server, but as it’s added to the form via a seperate web request there is no form context, and therefore client side validation won’t work.

There is a good explanation of how this all works on stackoverflow. I have taken the code here and wrapped it up into an extension method that looks like this:

public static class HtmlClientSideValidationExtensions
{
    public static IDisposable BeginAjaxContentValidation(this HtmlHelper html, string formId)
    {
        MvcForm mvcForm = null;

        if (html.ViewContext.FormContext == null)
        {
            html.EnableClientValidation();
            mvcForm = new MvcForm(html.ViewContext);
            html.ViewContext.FormContext.FormId = formId;
        }

        return new AjaxContentValidation(html.ViewContext, mvcForm);
    }

    private class AjaxContentValidation : IDisposable
    {
        private readonly MvcForm _mvcForm;
        private readonly ViewContext _viewContext;

        public AjaxContentValidation(ViewContext viewContext, MvcForm mvcForm)
        {
            _viewContext = viewContext;
            _mvcForm = mvcForm;
        }

        public void Dispose()
        {
            if (_mvcForm != null)
            {
                _viewContext.OutputClientValidation();
                _viewContext.FormContext = null;
            }
        }
    }
}

This then allows me to wrap any code I’m going to return with ajax with a using statement, so my editor template now becomes this:

<%
    using (Html.BeginAjaxContentValidation("form0"))
    {
        using (Html.BeginCollectionItem("PhoneNumbers"))
        { %>
    <div style="padding: 5px 0px 5px 0px">
        <%: Html.LabelFor(m => m.Type) %>
        <%: Html.EditorFor(m => m.Type) %>
        <%: Html.LabelFor(m => m.Number) %>
        <%: Html.EditorFor(m => m.Number) %>
        <%: Html.ValidationMessageFor(m => m.Number) %>
    </div>
<%
        }
    }
%>

The extension method requires the name of the form which it sets on the FormContext. By default this will be form0 if you have one form.

If you run the application now you’ll see that the hidden input for the client validation message will be rendered for the dynamically added field, where previously it wasn’t:

At this point you’ll still find the client side validation isn’t working. The final step is to add a line of JavaScript after adding the new field’s HTML to the DOM:

$().ready(function () {
    $("#add-phone").click(function () {
        $.ajax({
            url: '<%: Url.Action("GetNewPhone") %>',
            success: function (data) {
                $(".phone-numbers").append(data);
                Sys.Mvc.FormContext._Application_Load();
            }
        });
    });
});

Adding this will refresh the client side validation script including the script for the dyanamically added fields and client side validation will work as expected.

Download Source

Posted on by Joe in Ajax, C#, MVC

2 Responses to ASP.NET MVC 2 client side validation for dynamic fields added with ajax

  1. Nhat.Nguyen

    Thanks very much! i’m looking for this issue

  2. Alex

    How use it in MVC5?

Add a Comment