views:

1413

answers:

2

I've read Phil Haack's post on custom client-side validation in ASP.NET MVC 2. I want to do the same thing but with the jQuery adapter and using ASP.NET MVC 2 RC (as opposed to MVC 2 Beta that the post uses). Has anyone been able to figure how to do this?

I specially want to implement the password matching validation (i.e. password & confirm password must match). The ASP.NET MVC 2 RC VS.NET project template does show how to implement that on the server-side (using the PropertiesMustMatchAttribute) but not on the client-side.

+1  A: 

Here's how to add a custom jQuery validation:

$.validator.addMethod("noSpaces", function(value, element) {
    if ($(element).val().indexOf(" ") >= 0) {
        return false;
    } else {
        return true;
    }
}, "Value must not contain spaces");
Craig Stuntz
How can you integrate that custom method to the ASP.NET MVC 2's validation engine?
Buu Nguyen
Based on Phil's post, the name alone should do it. But I haven't tried it yet.
Craig Stuntz
+12  A: 

I assume you already followed Phil Haack's instructions here http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx) on how to get custom validation working with MS AJAX client validation. To get it to work with jQuery, you'll need to modify the MicrosoftMvcJQueryValidation.js file:

  • In the __MVC_CreateRulesForField(validationField) function, you'll need to add a case statement. Continuing Phil's example, you'll need to add:

    case "price":

    __MVC_ApplyValidator_Price(rulesObj, thisRule.ValidationParameters["min"]);

    break;

  • You'll then need to create the __MVC_ApplyValidator_Price function:

function __MVC_ApplyValidator_Price(object, value) {

// min is what jQuery Validate uses to validate for minimum values
object["min"] = value;

}

That should be enough to get Phil's example working.

Now, regarding your PropertiesMustMatchAttribute validation, it doesn't look like MVC generates the client-side json validation definition for attributes that decorate classes. Since PropertiesMustMatchAttribute must be used on the model (and not the property), I can't figure out how to make it trigger client-side validation. Instead, I took a different approach. I created a dummy validation attribute who's IsValid() overload always returns true, and used this attribute on a property. This is just a dummy attribute that will delegate the validation logic to jQuery validator's equalTo function. Here's the dummy attribute:

public class PropertiesMustMatchClientTriggerAttribute : ValidationAttribute
{
    public string MatchProperty { get; set; }

    public PropertiesMustMatchClientTriggerAttribute(string matchProperty)
    {
        MatchProperty = matchProperty;
        ErrorMessage = "{0} doesn't match {1}.";
    }
    public override bool IsValid(object value)
    {
        return true;
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, MatchProperty);
    }
}

Here is the custom validator:

public class PropertiesMustMatchClientTriggerValidator : DataAnnotationsModelValidator<PropertiesMustMatchClientTriggerAttribute>
{
    private string _message;
    private string _matchProperty;

    public PropertiesMustMatchClientTriggerValidator(ModelMetadata metaData, ControllerContext context, PropertiesMustMatchClientTriggerAttribute attribute)
        : base(metaData, context, attribute)
    {
        _message = attribute.FormatErrorMessage(metaData.DisplayName);
        _matchProperty = attribute.MatchProperty;
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = _message,
            ValidationType = "equalTo"
        };
        rule.ValidationParameters.Add("matchField", _matchProperty);

        return new[] { rule };
    }
}

the above custom validator needs to be registered in Application_Start() per Phil's blog:

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(PropertiesMustMatchClientTriggerAttribute), typeof(PropertiesMustMatchClientTriggerValidator));

Finally, you need to modify the MicrosoftMvcJQueryValidation.js file:

  • Add the following case statement to __MVC_CreateRulesForField:

case "equalTo":

__MVC_ApplyValidator_EqualTo(rulesObj, thisRule.ValidationParameters["matchField"]);

break;

  • add this function:

function __MVC_ApplyValidator_EqualTo(object, elemId) {

object["equalTo"] = document.getElementById(elemId);

}

Now you need to attach the dummy validation attribute to a property:

    [PropertiesMustMatchClientTrigger("Password")]
    public string ConfirmPassword { get; set; }

That should do it.

Creating this dummy attribute is a bit ugly, so I hope someone can come up with a more elegant solution.

JohnnyO
Thanks for the elaborated answer. I'll try that and update the result later.
Buu Nguyen
Just a quick follow-up on this. Based on Brad Wilson's talk on C4MVC (http://www.viddler.com/explore/c4mvc/videos/24/), the MVC team is considering implementing client-side validation for model-level annotations in MVC 3. If they do that, this type of problem will be much easier to solve.
JohnnyO
Finally I tried your solution and it works like charm. Many thanks!
Buu Nguyen
Great stuff here
Graphain