Site Meter
 
 

Validating a variable length list, ASP.NET MVC 2-style

In the previous post I showed a fairly straightforward way to create an editor where the user can add and delete the items in a set. Please read that previous post before continuing to read this one.

image

But what about validation? We don’t like your blank item names or your negative prices, sonny!

As you probably know, ASP.NET MVC 2 supports DataAnnotations attributes out of the box, so you can mark up your model as follows.

public class Gift
{
    [Required]
    public string Name { get; set; }
 
    [Range(0.01, double.MaxValue, ErrorMessage = "Please enter a price above zero.")]
    public double Price { get; set; }
}

Server-side validation

Server-side validation is trivial, assuming you want to use the default model binder convention of validating each model object when it’s bound. In the action that receives the form post, just check ModelState.IsValid, and refuse to accept the data if it isn’t.

[HttpPost]
public ActionResult Index(IEnumerable<gift> gifts)
{
    if (ModelState.IsValid)
        return View("Completed", gifts);
    else
        return View(gifts); // Redisplay the form with errors
}

You’ll also need to specify where the validation error messages should appear. Update the GiftEditorRow.ascx partial by placing a couple of Html.ValidationMessageFor() helpers somewhere inside the row.

<%= Html.ValidationMessageFor(x => x.Name) %>
<%= Html.ValidationMessageFor(x => x.Price) %>

Now, if the incoming data doesn’t satisfy your rules, the form will be re-rendered, displaying appropriate messages.

image

Client-side validation

It gets a bit more complicated if you also want client-side validation. You could use xVal (I’m not sure whether it would be easier or harder), but I want to get better acquainted with ASP.NET MVC 2’s built-in client-side validation feature, so I’m going to use that.

I don’t even know if there’s an officially recommended way of doing ASP.NET MVC 2 client-side validation when you’re dynamically adding and removing form elements, but I’ll show you a technique I found that will do it. Be warned: this is rather hacky. I’d be interested to hear if anyone can suggest a better way.

First, let’s set up client-side validation in the normal way. Enable client-side validation on your form by calling Html.EnableClientValidation():

<h2>Gift List</h2>
What do you want for your birthday?
 
<% Html.EnableClientValidation(); %>
<% using(Html.BeginForm()) { %>
    (rest as before)
<% } %>

Now, as long as you’ve already referenced MicrosoftAjax.js and MicrosoftMvcValidation.js, you can run the app and you’ll immediately get working client-side validation! But hang on a minute… it only works for the elements that are present when the form is first rendered, *not* the elements added when the user clicks “Add another…”.

We need a way to capture the extra validation rules added each time the user adds a new row and somehow attach them to the form. One possible approach is to create a new type of custom view result that registers the validators associated with any partial that is renders, and have it emit some JavaScript to attach those validators to the form.

Here’s a possible implementation. Don’t worry if you don’t understand it.

public class AjaxViewResult : ViewResult
{
    public string UpdateValidationForFormId { get; set; }
 
    public AjaxViewResult(string viewName, object model)
    {
        ViewName = viewName;
        ViewData = new ViewDataDictionary { Model = model };
    }
 
    public override void ExecuteResult(ControllerContext context)
    {
        var result = base.FindView(context);
        var viewContext = new ViewContext(context, result.View, ViewData, TempData, context.HttpContext.Response.Output);
 
        BeginCapturingValidation(viewContext);
        base.ExecuteResult(context);
        EndCapturingValidation(viewContext);
 
        result.ViewEngine.ReleaseView(context, result.View);
    }
 
    private void BeginCapturingValidation(ViewContext viewContext)
    {
        if (string.IsNullOrEmpty(UpdateValidationForFormId))
            return;
        viewContext.ClientValidationEnabled = true;
        viewContext.FormContext = new FormContext { FormId = UpdateValidationForFormId };
    }
 
    private void EndCapturingValidation(ViewContext viewContext)
    {
        if (!viewContext.ClientValidationEnabled)
            return;
        viewContext.OutputClientValidation();
        viewContext.Writer.WriteLine("<script type=\"text/javascript\">Sys.Mvc.FormContext._Application_Load()</script>");
    }
}

Now, we can change the BlankEditorRow() action method to render its partial using this custom view result.

public ViewResult BlankEditorRow(string formId)
{
    return new AjaxViewResult("GiftEditorRow", new Gift()) { UpdateValidationForFormId = formId };
}

You’ll notice that BlankEditorRow() now needs to be told which form it should attach the new validators to. You’ll have to update the link to BlankEditorRow to add this information to the query string.

<%= Html.ActionLink("Add another...", "BlankEditorRow", new { ViewContext.FormContext.FormId }, new { id = "addItem" }) %>

Et voila! Each time the user appends a new row, its validators will magically be associated with the form.

The problem with removing fields

If you proceed with this approach, you’ll soon discover a further flaw. Even if the user removes fields from the form, any validators associated with those fields will still be lurking.  If a field is displaying a “required” error message, and then the user deletes the containing row, it will become impossible to submit the form because the field is still required, except now there is nowhere to enter any data for it. Whoops!

Again, I don’t know if there’s a recommended way to deal with this, but one possibility is to edit the MicrosoftMvcValidation.debug.js script a little. Let’s update the logic slightly so that, if a validator is associated with fields that no longer exist, the validator should no longer apply.

Find the function called “Sys_Mvc_FieldContext$validate”, and right at the top, add the following:

// [Added] Permanently disable this validator if its associated elements are no longer in the document
for (var j = 0; j < this.elements.length; j++) {
    if (!Sys.Mvc.FormContext._isElementInHierarchy(document.body, this.elements[j])) {
        this.validations = [];
        break;
    }
}

Now, assuming you’re referencing the file you just edited (MicrosoftMvcValidation.debug.js, rather than MicrosoftMvcValidation.js), you should get the desired change in behaviour. Deleting a row in the editor now kills its associated validation rules properly, so they don’t rise from the grave and block form submissions like invisible validation zombies.

Summary

Once you’ve got the AjaxViewResult class, it doesn’t take that much work to enable client-side validation on dynamically changing forms. Maybe there’s a better and less hacky way though… Anybody got any suggestions?

42 Responses to Validating a variable length list, ASP.NET MVC 2-style

  1. Kerry Jenkins

    Thanks for these past two posts. They are very helpful and practical to those trying to learn. All of your contributions (book, blog, tekpub) are appreciated.

  2. Interesting. Thanks!

  3. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #529

  4. Gary McAllister

    Hey Steve, nice book..

    Have you tried targeting the client side validation script elsewhere in your views (head) ?

    I am currently not happy with the Html.EnableClientValidation() cruft injected into my page.

    Cheers

  5. I Have MVC 2 but Demo code wont work Beacause of Writer not exists in ViewContext. I have the same comment on previous post. Is it possible you change the source of MVC and build a new version for yourself ?
    So include your demo project with the latest build of MVC you use (may on a required bin folder) or may I miss something.

  6. Magne

    Hi. Great posts!
    I’m trying to use this in my project, but I am having some problems.

    I don’t want to use attributes for my validation, because I have a lot of special validation that query the database etc. I have my own validators that take care of all validation. I don’t validate editmodels(I’m mapping editmodels to commands). Could you point me in some direction on how to manually add errors to the modelstate with the correct key(id), so the error-message would be associated with the correct input? For single(only one editmodel) input I simply add a modelerror with the property name as key.

    I hope you understand my question :)
    Thanks!

  7. Jon

    Just reading Brad Wilson’s post on remote validation; would this be an easier solution?

  8. Steve

    Gary – I’m not sure if Html.EnableClientValidation-style forms provide any neat way of rendering the JavaScript in an arbitrary place. They certainly wouldn’t put it in the head, because the validators haven’t even been defined by the time the head section is rendered. These days most people prefer to put their JavaScript at the bottom of the page (not in the head), which might theoretically be possible but I’m not sure right now how you might do that. But is this just a matter of preference, or does it actually cause a practical problem?

    Ali – my demo project works with ASP.NET MVC 2.0 Release Candidate. I haven’t modified System.Web.Mvc.dll at all. Can you try removing your reference to System.Web.Mvc.dll, and re-adding the reference to \Program Files\Microsoft ASP.NET\ASP.NET MVC 2\Assemblies\System.Web.Mvc.dll (or wherever the MVC 2 DLL is on your PC)?

    Magne – In your action method, use a line of code similar to ModelState.AddModelError(key, errorMessage);. The key value must match the fully-qualified property name (e.g., “gifts[someid].Name”). You can reference a property on a nested model object with a key such as “modelname.propertyname.subpropertyname.subsubpropertyname”.

    Jon, I don’t see how remote validation would make this any easier. The challenge is associating the validators for dynamically-added elements with the form. I think the same challenge would apply even if you’re using remote validation.

  9. Thanks Steve it worked. I Had MVC 2 Beta . When updated to RC Error gone.
    Thanks again for great post.

  10. Joo Park

    skip client side validation through asp.net mvc altogether and just implement your own client side validation. If it’s too hacky, better, to write clean code despite duplicating effort.

  11. firefly

    In JQuery there is a live function that allow you to bind into future form. I would imagine something like that would be a good fit for this case.

  12. Alex White

    Hi Steve, I’m having difficulty using attributes to ensure datetime fields are valid. I’ve read that the [Range ] attribute can be used to do the job, but I’ve not had any success with that. Because it is the ‘release date’ for a page, I need to have both the date and the time in dd/mm/yy hh:mm format validated.

    Do I have to build something myself for this, or is there an existing solution in MVC2?

    Thanks

  13. Rick

    Excellent work. Validation for dynamically loaded content, what I was looking for a long time. Thank you very much!

  14. a.in.the.k

    What about pure server side (no JS) solution with add, delete functionality ?
    Is there any nice article ?

    patterns for deleting collection items from model and updating ModelState as well etc …
    Thanx

  15. Nice of you to take the time sharing this with us, much appriciated

  16. Petr Snobelt

    Hi, first of all thank you for nice article.
    I like to have 1 new empty line ready to start typing and form should save if this row is empty, but I can’t find how to do this. Have you any idea how I can do this?

  17. Fredrik

    Your posts are amazingly useful! I end up using both this approach as well as your older post: jQuery Ajax uploader plugin (with progress bar! from 2008 in my latest MVC 2 project. Thank you for saving me lots of hours!! :)

  18. Muhammad Adeel

    Thanks steve for wonderful post. i have a problem though with ur previous post. i wanted to bind array of checkboxes to a collection of complex types. i used ur code and put Html.BeginCollections. it binds checkboxes with complex type but at the same time it associates true/false for (checked/unchecked) with ID field of Model hence making modelstate Invalid. i don’t know if i m doing something wrong or u did not consider the scenario of checkboxes

  19. Pingback: Editing a variable-length list, Knockout-style « Steve Sanderson’s blog

  20. Evan

    I’m really curious about the solution to using EditorFor() with a 0-based numerical index.

  21. Johnson

    I have a problem of not able to access ViewData in my partial with this code. I set the ViewData in my action before calling AjaxViewResult but also I getting error while accessing the ViewData. Can someone tell me how to do it?

  22. Mohammad

    I have a problem when two same user controls which have validation in them inserted in a page through RenderPartial. RenderPartial does not generate new ids for inputs therefore validation does not work.
    I’ve posted the question on Stackoverflow, but it seems there is no clear solution for this case. Here is the link to the stackoverflow question.

    http://stackoverflow.com/questions/3503273/validation-does-not-work-when-two-instances-of-user-control-exists-on-the-view

    Any thoughts appreciated.
    Mohammad

  23. Jon Jenkins

    I’ve been trying to get some custom validation to work with a variable length list using this method combined with the custom validator + client-side JQuery validation handlers referenced at http://blogs.msdn.com/b/simonince/archive/2010/06/11/adding-client-side-script-to-an-mvc-conditional-validator.aspx … however, only the server-side validation seems to react. I’m not sure if you’ve tried a custom validator with this method yet? Basically I am seeking to use conditional validation (think “requiredif” or “requiredwhencontains”) in a variable length list. I can provide my source if it is helpful, but the MSDN link above–using the JQuery script case–should be enough to see if anyone’s done this?

  24. Jon Jenkins

    http://stackoverflow.com/questions/3597695/using-a-custom-validator-in-a-variable-length-list-in-microsoft-mvc-2-client-sid/3628249#3628249

    This may help anyone trying to use custom validators with the variable list method and collection helper provided in these tutorials… I finally got client-side validation working with custom validators. The element ID naming was the issue.

  25. TimWilliams

    Steve,

    Many thanks for this. I Googles my way here and I’m sure this demo project is sitting on many a developer’s hard drive!

    Just one question from a JavaScript numpty: is it really the only way to remove the unnecessary validation? Can nothing be put in the delete handler for the row being deleted?

    Keep up the good work.

  26. How about updating this for mvc 3 and unobtrusive validation

  27. Steve

    Sean, the approach of trying to keep an entire history of posts up-to-date just doesn’t scale I’m afraid. There’s no way I’d ever be able to write new ones if I did that. Sorry! All my posts are point-in-time snapshots of the way things were at the time they were written, like newspaper articles.

  28. Remus

    Steve, if you do have a working sample of the same thing but updated to work with MVC 3, unobtrusive validation, can you send me a quick sample project? Do you still have to go through the same hoops with client validation, or do you get that for “free” with MVC 3, given the updated infrastructure for data annotations?

  29. Chris

    Steve, thanks for your excellent post. If you have a working sample for MVC 3 as mentioned by Remus, I would love to get a copy of it also. For now I’ll work on a solution.

  30. JiPe-Freckles

    After several hours in google i found you example and i must say : Excellent job!

  31. ncsuwolf

    Steve, I was also wondering if there was any chance that you were going to convert this to MVC 3 and Razor? Excellent work on this one!

  32. Micky

    Hi Steve,

    I was wondering if you knew of any tutorial would show the Entity Framework code to persist the changes made in this list back to the database. I’m relatively new to MVC and Entity Framework and would really like to see how you would create, update and delete entities using this pattern.

    many thanks for the great work!

  33. ncsuwolf

    I am currently having issues with anything that requires jQuery in the dynamically added partials… Validation, datepickers, etc… The first row loaded when the parent page loads seems to work, but each subsequent row, unless I add the jQuery to the partial, doesn’t work. Has anyone else seen behavior like this before?

    Thanks,

    ncsuwolf

  34. Bodhisatta Das

    This works great except for view data.I am still unable to link viewdata to the newly formed entity with index

  35. @Steve i have used your BeginCollectionItem helper in conjunction with jquery templating engine to enable master detail form without the need of ajax call for bringing empty row (e.g gift editor row). i would request your valuable comments on http://zahidadeel.blogspot.com/2011/05/master-detail-form-in-aspnet-mvc-3-i.html

  36. hi steve!
    first of all, thank you very much for these two wonderful posts. i bet they helped a lot of people to get dynamic item adding work.

    however, i encouter one problem using client validation: the items themselves are being validated, and the validation messages are rendered for each of them. unfortunately, html.validationsummary() still only shows the validation messages for the items being rendered initially.

    maybe you have any advice on how to get validationsummary working?

  37. Sadie Christoff

    ÿþ|

  38. Michael Kintu

    Dear Steve, thanks for the posts. Very interesting. I am trying to convert this into a Master/Details form scenario using MVC 2.0.

    Do you have any ideas?

    Thanks

  39. parajao

    For client-side validation using Html.BeginCollectionItem with razor look this blog post: http://stackoverflow.com/questions/7839453/steve-sandersons-begincollectionitem-not-working-in-all-cases-potential-solu

  40. Volodymyr Gotra

    Excellent solution. Thank you Steven!

    Also with AjaxViewResult you can just update jQuery validator for form after each insert or remove of dynamic element (if you don’t want to use MVC Validation script)

    function updateFormValidation(selector){
    $(selector).removeData(‘validator’);
    $.validator.unobtrusive.parse($(selector));
    }

    and remove in AjaxViewResult class next line

    viewContext.Writer.WriteLine(“Sys.Mvc.FormContext._Application_Load()”);

  41. Thanks, that was exceptionally good post. Love the way you express your thoughts.