ASP.NET MVC Validation Groups?

2019-01-22 21:36发布

问题:

I have a form in which I would like to require different fields depending on which submit button is pressed. Example: Field A is required if you press Submit Button 1, but only Field B is required if you press Submit Button 2. If I was still using web forms, I would assign different "validation groups" to each button/validator combination. Is there a way to do this in MVC, preferably with data annotations on the model? I would prefer to implement client and server validation with a single solution, but I will take what I can get...

Thanks in advance!

回答1:

How about a custom validation attribute with client validation enabled (so you get both client and server validation)? The solution below uses jQuery unobtrusive validation. To use this you will need to give all your buttons specific names and pass the name to the validation attribute. The button will also need to have a value of some sort so it can be posted back (so the server side code can test it, i.e. <input type="submit" name="myButton" value="1" />). I haven't tested this code, so I'm not sure if it runs out of the box. You may need to make some mods:

The validation attribute for your model:

public class RequiredIfButtonClickedAttribute : ValidationAttribute, IClientValidatable
{
    private RequiredAttribute _innerAttribute = new RequiredAttribute();
    public string ButtonName { get; set; }

    public RequiredIfButtonClickedAttribute(string buttonName)
    {
        ButtonName = buttonName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if ((value == null && !string.IsNullOrEmpty(HttpContext.Current.Request.Form[ButtonName])))
        {
            if (!_innerAttribute.IsValid(value))
            {
                return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
            }
        }

        return ValidationResult.Success;
    }

    #region IClientValidatable Members

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule() { ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), ValidationType = "requiredifbuttonclicked" };
        rule.ValidationParameters.Add("buttonname", ButtonName);
        yield return rule;
    }

    #endregion
}

The client script:

/// <reference path="jquery-1.4.4-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />

// When a button is clicked remove the clicked button class from all buttons and add it to the on that was clicked
$(":submit").click(function () {
    $(":submit").removeClass('clickedButton');
    $(this).addClass('clickedButton');
});

$.validator.addMethod('requiredifbuttonclicked',
    function (value, element, parameters) {

        // if the condition is true, reuse the existing 
        // required field validator functionality
        if ($(".clickedButton").val("name") === parameters['buttonname'])
            return $.validator.methods.required.call(
              this, value, element, parameters);

        return true;
    }
);

$.validator.unobtrusive.adapters.add(
    'requiredifbuttonclicked',
    ['buttonname'],
    function (options) {
        options.rules['requiredifbuttonclicked'] = {
            buttonname: options.params['buttonname']
        };
        options.messages['requiredifbuttonclicked'] = options.message;
});

And use it like this:

[RequiredIfButtonClicked("myButtonName")]
public string Name { get; set; }


回答2:

You could give the same name to each submit button and different value. Then have a property on your view model having this name of type string. When the form is submitted its value will match the value of the button that was clicked. Now you could design a custom validator attribute that will be used to decorate your view model with. In its IsValid implementation you will fetch the instance of your view model and based on the value of the special property you will perform the validations. It's ugly, I know, but DataAnnotations are really useful for simple validation situations, but when you start writing real world applications you realize their limitations.

Personally I use FluentValidation.NET and a scenario like the one being described here is pretty trivial to implement.