7

I'm currently trying to work through MVC validation, and am coming up against some problems where a field is required depending on the value of another field. An example is below (that I haven't figured out yet) - If the PaymentMethod == "Cheque", then the ChequeName should be required, otherwise it can be let through.

[Required(ErrorMessage = "Payment Method must be selected")]
public override string PaymentMethod
{ get; set; }

[Required(ErrorMessage = "ChequeName is required")]
public override string ChequeName
{ get; set; }

I'm using the System.ComponentModel.DataAnnotations for the [Required], and have also extended a ValidationAttribute to try and get this working, but I can't pass a variable through to do the validation (extension below)

public class JEPaymentDetailRequired : ValidationAttribute 
{
    public string PaymentSelected { get; set; }
    public string PaymentType { get; set; }

    public override bool IsValid(object value)
    {
        if (PaymentSelected != PaymentType)
            return true;
        var stringDetail = (string) value;
        if (stringDetail.Length == 0)
            return false;
        return true;
    }
}

Implementation:

[JEPaymentDetailRequired(PaymentSelected = PaymentMethod, PaymentType = "Cheque", ErrorMessage = "Cheque name must be completed when payment type of cheque")]

Has anyone had experience with this sort of validation? Would it just be better to write it into the controller?

Thanks for your help.

2
  • On second thought... How are you able to set the PaymentSelected = PaymentMethod? You should be getting an error because PaymentMethod isn't a constant expression. Commented Jan 5, 2010 at 23:34
  • Hi Min, you're right. I thought that I might be able to do it this way but it doesn't work. I just wanted to show what I had tried, but also commented that it wouldn't allow me to pass through the variable. Commented Jan 6, 2010 at 0:44

4 Answers 4

4

If you want client side validation in addition to model validation on the server, I think the best way to go is a custom validation attribute (like Jaroslaw suggested). I'm including the source here of the one I use.

Custom attribute:

public class RequiredIfAttribute : DependentPropertyAttribute
{
    private readonly RequiredAttribute innerAttribute = new RequiredAttribute();

    public object TargetValue { get; set; }


    public RequiredIfAttribute(string dependentProperty, object targetValue) : base(dependentProperty)
    {
        TargetValue = targetValue;
    }


    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // get a reference to the property this validation depends upon
        var containerType = validationContext.ObjectInstance.GetType();
        var field = containerType.GetProperty(DependentProperty);

        if (field != null)
        {
            // get the value of the dependent property
            var dependentvalue = field.GetValue(validationContext.ObjectInstance, null);

            // compare the value against the target value
            if ((dependentvalue == null && TargetValue == null) ||
                (dependentvalue != null && dependentvalue.Equals(TargetValue)))
            {
                // match => means we should try validating this field
                if (!innerAttribute.IsValid(value))
                    // validation failed - return an error
                    return new ValidationResult(ErrorMessage, new[] { validationContext.MemberName });
            }
        }

        return ValidationResult.Success;
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
                       {
                           ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
                           ValidationType = "requiredif"
                       };

        var depProp = BuildDependentPropertyId(DependentProperty, metadata, context as ViewContext);

        // find the value on the control we depend on;
        // if it's a bool, format it javascript style 
        // (the default is True or False!)
        var targetValue = (TargetValue ?? "").ToString();
        if (TargetValue != null)
            if (TargetValue is bool)
                targetValue = targetValue.ToLower();

        rule.ValidationParameters.Add("dependentproperty", depProp);
        rule.ValidationParameters.Add("targetvalue", targetValue);

        yield return rule;
    }
}

Jquery validation extension:

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

$.validator.addMethod('requiredif',
    function (value, element, parameters) {
        var id = '#' + parameters['dependentproperty'];

        // get the target value (as a string, 
        // as that's what actual value will be)
        var targetvalue = parameters['targetvalue'];
        targetvalue = (targetvalue == null ? '' : targetvalue).toString();

        // get the actual value of the target control
        var actualvalue = getControlValue(id);

        // if the condition is true, reuse the existing 
        // required field validator functionality
        if (targetvalue === actualvalue) {
            return $.validator.methods.required.call(this, value, element, parameters);
        }

        return true;
    }
);

Decorating a property with the attribute:

[Required]
public bool IsEmailGiftCertificate { get; set; }

[RequiredIf("IsEmailGiftCertificate", true, ErrorMessage = "Please provide Your Email.")]
public string YourEmail { get; set; }
Sign up to request clarification or add additional context in comments.

1 Comment

I realize this answer is 2 years old but I am trying to get this to work and basically it fires the validation on the dependent property no matter what the value of the first property is. Any help would be appreciated.
4

Just use the Foolproof validation library that is available on Codeplex: https://foolproof.codeplex.com/

It supports the following "requiredif" validation attributes / decorations:

[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]

To get started is easy:

  1. Download the package from the provided link
  2. Add a reference to the included .dll file
  3. Import the included javascript files
  4. Ensure that your views references the included javascript files from within its HTML for unobtrusive javascript and jquery validation.

Comments

3

I would write the validation logic in the model, not the controller. The controller should only handle interaction between the view and the model. Since it's the model that requires validation, I think it's widely regarded as the place for validation logic.

For validation that depends on the value of another property or field, I (unfortunately) don't see how to completely avoid writing some code for that in the model, such as shown in the Wrox ASP.NET MVC book, sort of like:

public bool IsValid
{
  get 
  {
    SetRuleViolations();
    return (RuleViolations.Count == 0); 
  }
}

public void SetRuleViolations()
{
  if (this.PaymentMethod == "Cheque" && String.IsNullOrEmpty(this.ChequeName))
  {
    RuleViolations.Add("Cheque name is required", "ChequeName");
  }
}

Doing all validation declaratively would be great. I'm sure you could make a RequiredDependentAttribute, but that would only handle this one type of logic. Stuff that is even slightly more complex would require yet another pretty specific attribute, etc. which gets crazy quickly.

1 Comment

thanks djuth, I've taken a ModelStateDictionary and done validation with this in the model, and then passed the dictionary back to the controller to merge into the ModelState. Seems to do the trick and allows me to do some programmatic work - not quite as nice just doing a declaration per attribute, but at least I can get everything in the one spot. Not sure how this will go if there's more than one error per attribute tho.
3

Your problem can be solved relatively simply by the usage of conditional validation attribute e.g.

[RequiredIf("PaymentMethod == 'Cheque'")]
public string ChequeName { get; set; }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.