nav-left cat-right
cat-right

Manually validate an ASP.Net MVC form on the client side with MicrosoftMvcValidation.js and jQuery

ValidationA recent problem cropped up in my Asp.Net MVC application. Its using the standard setup of DataAnnotations + MicrosoftMvcValidation.js + jQuery 1.4.2, and I needed to check the validation state of a form before performing some client-side actions. No problem, right?

Obviously not.

This second part of this post gets in depth as to what's going on with the MicrosoftMvcValidation script, but to save you some time, I'll post the solution first.

To manually validate your form with MicrosoftMvcValidation.js:

In MicrosoftMvcValidation.js, change line 20 from:

return Sys.Mvc._ValidationUtil.$0($2.validate('submit'));})); return $2;

to

return Sys.Mvc._ValidationUtil.$0($2.validate('submit'));})); $0.MvcValidationFormContext = $2; return $2;

Alternately, if you're using the debug version of the script, you can achieve the same effect by adding the following to line 196 (the end of the _parseJsonOptions function):

formElement.MvcValidationFormContext = formContext;

UPDATE: As Morten Christiansen points out in the comments, there is a much cleaner way of accessing the FormContext object, without having to introduce a custom tracking property and modifying the script. Instead of a custom property, we can just use
$('form')[0]['__MVC_FormValidation']. Thanks to Morten for pointing this out!

Now we can use the following jQuery/javascript code to manually validate our form:

var $form = ("#MyForm");                            // Select our form with jQuery
var context = $form[0].MvcValidationFormContext;    // Access the new property we created
var errors;
if ($form[0]['__MVC_FormValidation']) {
    errors = $form[0]['__MVC_FormValidation'].validate("submit");        // Validate the form
}
if (!$form[0]['__MVC_FormValidation'] || errors.length == 0) {
    // No errors, do your stuff
}

Now for the explanation.

I assumed (incorrectly) that there must be a client-side API to MicrosoftMvcValidation.js, the javascript responsible for dynamic client side form validation that ships with the ASP.Net MVC framework. Unfortunately, in all my digging, I could not unearth one -- and it seems like a huge oversight. The amount of effort to expose an isValid property or a validate() function on the client would be pretty minimal!

Why would you need to do this? Doesn't the validation occur automatically? Well, yes it does -- when you submit the form, or click around in it. However, there are lots of cases where you might want to manually trigger the error messages or perform a client-side action based on whether or not the form is valid, without running the submit handler. In my case, I wanted to make sure the form was valid before trying to submit it, as our ajax pipeline puts up the "spinner" graphic when a submit event is triggered, which meant it was flashing briefly and looked dumb.

The most common response I ran across when searching for how to manually validate was -- give up and use the jQuery validate plugin. This would be my preferred solution to be honest, but I ran into at least two blockers with jQuery validate:

  1. it doesn't seem to work with modal dialogs generated from jQuery UI,
  2. it doesn't seem to work with forms that are loaded into the document via an ajax call.

So that was out. Since I already had most of what I needed working with the Microsoft scripts, I went back to them and started exploring the code to see if I could discover any sort of public API. This is what I came up with.

The key thing that I found was that MicrosoftMvcValidation creates a javascript object called a FormContext, which has a validate(eventName) method. This seemed promising, so I tried following the code to see how I could get at this validate method. Well -- I couldn't. Not without some modifications. Here's how it works.

When you include MicrosoftMvcValidation, the main piece of script that executes is this:

Sys.Mvc.FormContext._Application_Load = function Sys_Mvc_FormContext$_Application_Load() {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            Sys.Mvc.FormContext._parseJsonOptions(thisFormOptions);
        }
    }
}

Adding <% Html.EnableClientValidation(); %> to your views causes a bunch of JSON data to be output to the page, and appended to the window.mvcClientValidationMetatdata javascript object. When the page is ready, the above function runs and calls FormContext._parseJsonOptions() on all the rules and data that were output to that object.

_parseJsonOptions is what creates the FormContext object that we're interested in. In fact, the return value of _parseJsonOptions is the FormContext… but sadly, Application_Load ignores the return value, and it's just thrown to the bitbucket, never to be seen again (at least by us).

Judging from the underscores on the function names, it's pretty clear that these are meant to be internal operations, so we can (perhaps) excuse them for not storing and exposing the FormContext for us. From the framework's point of view, it doesn't need to be stored anywhere, because it still has it -- _parseJsonOptions internally adds new event handlers for the form, and these handler delegates close over the FormContext object. The closures means that internally, the form handlers still have a saved reference to what they need, so the application load function doesn't need to do anything with the return value. The FormContext never surfaces -- so if we want to validate the form ourselves, we need to expose dig for it.

All that my code at the top of the post is doing is modifying the _parseJsonOptions function (minified to be named $12), to start adding a new property to the form element in the DOM, which will store the created FormContext object. I named this property MvcValidationFormContext, but you could call it anything you'd like. You can subsequently access this new property to get the FormContext and call validate("submit") on it, which fires the validation as though we were trying to submit the form, and returns a collection of errors we can check.

As Morten pointed out, the reference is, of course, still there. You can access the closed over FormContext by grabbing the DOM element and digging in it's properties array. The updated code reflects this.


I'd love to see an upgrade to MicrosoftMvcValidation.js to include a client API in the future. Even more, I'd love to see jQuery Validate get some attention to fix the blockers that I ran into, so I can dump the MicrosoftAjax scripts out of my project altogether.

Be Sociable, Share!

18 Responses to “Manually validate an ASP.Net MVC form on the client side with MicrosoftMvcValidation.js and jQuery”

  1. Vlad says:

    Great post, Morgan!

    looks like you have taken a really deep look at MicrosoftMvcValidation.js

    May be you can help me in my scenario?:

    I am trying to disable/enable CRUD buttons depending on a state of a form. Form can be either valid or invalid:
    function validForm(form) {
    var errs = Sys.Mvc.FormContext.getValidationForForm(form).validate('submit');
    return (!(errs && errs.length));
    }

    However, what I would like to do is to have validForm as an event handler to some event, like onValidated. Is there anything in MicrosoftMvcValidation.js what would tell me that validation happened and validation state has changed?

    Thank you,
    Vlad.

  2. Morgan says:

    Vlad – interesting idea, and again, something that would have been quite useful and should have been exposed IMO.

    Unfortunately there are no hooks that I can find in MicrosoftMvcValidation.js that would allow for something like this.

    There are markers that it sets once a field has had validation triggered on it though. You can see this in action by using firebug, you'll see that input fields gain a DOM property called _MVC_HasValidationFired with a value of "true". This kind of static checking is unlikely to meet your needs, so the only other thing I can suggest is to start hacking at the debug version of the script.

    It looks like you could probably accomplish what you need pretty simply by adding a new function to the FormContext object. Try something like this:

    – After the "validate: " function, add a new function prototype, called onValidate. It's around line 328 of the debug version of the script.
    – In the end of the validate function, call your new function. Here's maybe what the changes could look like:

    325 }
    326 this.onValidate();
    327 return errors;
    328 }
    329
    330 onValidate: function () { }; //placeholder

    Then you could replace the placeholder and hook up your own function to "onValidate" when you get a reference to the FormContext.

  3. Chad says:

    Thank you. Thank you. Thank you.

  4. Jorrit says:

    Creat post, helped me a lot. I also hope there wil be a client API in the future, it would make things a lot easier.

  5. Vlad says:

    Morgan,
    I've finally got our CRUD buttons state logic working.
    Here is what I added to MicrosoftMvcValidation.debug.js:

    1. to Sys_Mvc_FormContext$_parseJsonOptions right before return formContext;:
    formElement.MvcValidationFormContext = formContext;

    2. to Sys.Mvc.FormContext.prototype:
    onValidationSateChanged: function Sys_Mvc_FormContext$onValidationSateChanged(formIsValid) {
    },

    3. to validate: function Sys_Mvc_FieldContext$validate(eventName) {:
    if ((this._errors.length == 0) && (errors.length > 0)) {
    this.formContext.onValidationSateChanged(false);
    }
    if ((this._errors.length > 0) && (errors.length == 0)) {
    this.formContext.onValidationSateChanged(true); //"becoming valid, event: " + eventName);
    }

    this._markValidationFired();
    this.clearErrors();
    this.addErrors(errors);
    return errors;

    And here is how I hook up CRUD state logic:

    function SetCRUDStateLogicForForm(formName) {

    //get the form
    var form = $("#" + formName);

    //get the form's context
    var context = form[0].MvcValidationFormContext;

    //hook up CRUD UI controls state logic to the form's validataion state change event
    context.onValidationSateChanged = SetCRUDStateForForm;

    //validate the form
    errors = context.validate("submit");

    //set form specific CRUD state
    SetCRUDStateForForm(errors.length == 0);
    }

    //Default UI CRUD controls state logic. Relies on naming convention.
    //It requires AddButton, saveButton, deleteButton and IsNew form elements to be present somewhere on a page
    function SetCRUDStateForForm(formIsValid) {
    var addButton = $("#AddButton");
    var saveButton = $("#SaveButton");
    var deleteButton = $("#DeleteButton");

    if (!formIsValid) {
    saveButton.attr("disabled", "disabled");
    deleteButton.attr("disabled", "disabled");
    }
    else {
    saveButton.removeAttr("disabled");
    if ($("#IsNew")[0].value != "True") {
    deleteButton.removeAttr("disabled");
    }
    }

    if ($("#IsNew")[0].value == "True") {
    addButton.attr("disabled", "disabled");
    }
    else {
    addButton.removeAttr("disabled");
    }
    }

    Hopefully it is somewhat useful.
    Cheers,
    Vlad.

  6. As far as I can see you can get to the form context without modifying the MicrosoftMvcValidation.js file like this:

    $('form')[0]['__MVC_FormValidation']

  7. Thanks for this great post. Very helpful.

    I'm pulling my forms down through Ajax calls. So I added the Sys.Mvc.FormContext._Application_Load function to my main window in order to regenerate the validation info, given that I don't have a recurring page load event.

    // Ryan Brubaker
    // Senior Software Engineer
    // (626) 379-4454
    // rbrubaker@artlogic.com
    // Art & Logic, Inc. – software development outsourcing since 1991
    // http://www.artlogic.com

  8. Kin Shing Choy says:

    Hello,

    MVC 2 allows client side validation using the model data annotations by using the MicrosoftMvcValidation.js.

    In this case when filling a value in a input box, client side validates the value.
    If it is not valid, the ErrorMessage in de data annotiontion will be shown next to the input box.
    Is it possible to show an image instead of the ErrorMessage string next to the input box?

    Kind regards,

    Kin Shing Choy

  9. Alexandre Salsinha says:

    var f = $("#formId");
    var context1 = f[0]['__MVC_FormValidation'];
    var errors;
    if (context1) {
    errors = context1.validate("submit"); // Validate the form
    }
    if (!context1 || errors.length == 0) {
    $.ajax(
    {
    type: "POST",
    url: f.attr("action"),
    data: f.serialize(),
    dataType: "json",
    success: function (result) {
    var domElement = $(result.Html);
    $("#elementToUpdateId").replaceWith(domElement);
    }
    });
    }

    This worked nicely for me. Thanks for your help.

  10. Morgan says:

    @Morten Christiansen – thanks for pointing this out! If I had gone just a bit farther with my explorations, I would have found this much better solution.

    Relying on "internal" javascript variables (classically differentiated by beginning with underscores) is of course never recommended – but using this method will perhaps survive an upgrade, whereas my original method would always require you to re-implement it when you got a new version of the script.

    I'll update the post with this.

  11. Gabriel Bonacim says:

    Great post, saved my life

  12. Morgan, This is wonderful. I was looking all over for a way to make sure the client-side validations got called before I did an Ajax post. I certainly didn't want to give up the MVC client-side validations in order to implement a progress bar. Thanks for posting such a clean elegant solution.

  13. Rick Kamp says:

    I have been looking for this answer all day! Thanks for making it so simple. It worked great for me, too.

  14. http://tinyurl.com/75qnout says:

    I have a video that you might be interested in. If you are like me, you are probably interested in automation. With the use of commission robotics, you can have your marketing done for you through complete automation. A series of robots performs important, methodical tasks, one after the other and allows you to focus on other aspects of internet marketing. It is really interesting how all of these little robots work in unison to perform all of these tasks. Even though this is extremely interesting to me, I think that it will be interesting for others as well and so here is the link to the video: http://goo.gl/v8qVq

  15. Eric says:

    Found this just in time, was about to give up and add some pre-client side validation.

    Not too hard to figure out, but I think
    "var $form = ("#MyForm");"
    should have the jQuery selector:
    "var $form = $("#MyForm");"

  16. Matt says:

    Thank you! Great post.

  17. Marcos says:

    In my case $('form')[0]['__MVC_FormValidation'] is unassigned. Anyone has any idea why?
    $('form')[0] does give me a form element, so that's not the problem.

Leave a Reply