How does DataAnnotations really work in MVC?

2019-01-13 06:42发布

问题:

This is more of a theoretical question.

I'm currently examining the MVC 3 validation by using ComponentModel.DataAnnotations, and everything works automagically, especially on client side.

Somehow something checks for those attributes, and generates javascript for the validation (or html5 attributes, if using unobtrusive mode), and it works.

My question is that what generates the client side javascript and how can I access and modify it? For example I want to handle the given dataannotation attributes a little differently, or handle custom attributes (I have found that I can derive them from ValidationAttribute, but maybe for some reason I don't want).

Can someone explain it to me what really happens? (Or links to good explanations would also be good, as I have only found tutorials for actually using dataannotations)

EDIT: Also with deriving from ValidationAttribute, the client-side validation is not working automatically. Why?

回答1:

MVC3 has a new jQuery Validation mechanism that link jQuery Validation and Validation Attributes Metadata, this is the jquery.validate.unobtrusive file that takes all data- attributes and work with them, just like before when you set the

<add key="UnobtrusiveJavaScriptEnabled" value="false" />

All you need to do is come up with your own Custom Validation Attributes, for that you have 2 options:

  • Create a Custom Validation Attribute that inherits the ValidationAttribute interface and override the IsValid

or

  • Create a Self Validate Model use the model IValidatebleObject that all you need is to return the Validate method

in MVC3 you now have a method that you can override that has a ValidationContext object, where you can simply get all references, properties and values of any other object in the form

Create your own, and that unobtrusive file will handle the mapping of what your custom validator needs and will work out together with the jQuery Validation plugin.

YOU DO NOT Change the javascript... that's sooo 90's and not MVC way!

for example if you want to validate, let's say 2 dates that the last can not be less than the first (period of time for example)

public class TimeCard
{
    public DateTime StartDate { get; set; }

    [GreaterThanDateAttribute("StartDate")]
    public DateTime EndDate { get; set; }
}

creating a Custom Validation

public class GreaterThanDateAttribute : ValidationAttribute
{
    public string GreaterThanDateAttribute(string otherPropertyName)
        :base("{0} must be greater than {1}")
    {
        OtherPropertyName = otherPropertyName;
    }

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

    public override ValidateionResult IsValid(object value, ValidationContext validationContext)
    {
        var otherPropertyInfo = validationContext.ObjectTYpe.GetProperty(OtherPropertyName);
        var otherDate = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
        var thisDate = (DateTime)value;

        if( thisDate <= otherDate )
        {
            var message = FormatErrorMessage(validationContext.DisplayName);
            return new ValidationResult(message);
        }

        return null;        
    }    
}

if using the Self Validating model then the code would be just

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if( EndDate <= StartDate )
        yield return new ValidationResult("EndDate must be grater than StartDate");
}

Keep in mind that the Custom Validation is Generic, that's why much code, and Self Validating Model only works on the model applied.

Hope it helps


added

I didn't explain the Custom Client Validation part, fell free to ask if you need examples, but basically:

It's easier in MVC3 (if of course, you understand jQuery.Validate) all you need to do is:

  • Implement IClientValidateble
  • Implement a jQuery validation method
  • Implement an unobtrusive adapter

To create this 3 things, let's take this GreaterThanDateAttribute into account and create the Custom Client Side Validation. For that we need to code this:

append to the GreaterThanDateAttribute

public IEnumerable<ModelCLientValidation> GetCLientValidationRules(ModelMetadata metadata, ControllerContext context)
{
    var rule = new ModelCLientValidationRule();
    rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
    rule.ValidationType = "greater"; // This is what the jQuery.Validation expects
    rule.ValidationParameters.Add("other", OtherPropertyName); // This is the 2nd parameter

    yield return rule;
}

Then you need to write the new jQuery Validator and the metadata adapter that will link the jQuery.Validation with your code providing the correct data- attributes for that field (if of course, UnobtrusiveJavaScriptEnabled is true)

create a new js file and attach to your <head> for example as

<script src="@Url.Content("~/Scripts/customValidation.js")" type="text/javascript"></script>

and append the new validation

jQuery.validator.addMethod("greater", function(value, element, param) {
    // we need to take value and compare with the value in 2nd parameter that is hold in param
    return Date.parse(value) > Date.parse($(param).val());
});

and then we write the adapter

jQuery.validator.unobtrusive.adapters.add("greater", ["other"], function(options) {
    // pass the 'other' property value to the jQuery Validator
    options.rules["greater"] = "#" + options.param.other;
    // when this rule fails, show message that comes from ErrorMessage
    options.messages["greater"] = options.message;
});