Reputation: 724
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.
Upvotes: 7
Views: 10531
Reputation: 17064
Your problem can be solved relatively simply by the usage of conditional validation attribute e.g.
[RequiredIf("PaymentMethod == 'Cheque'")]
public string ChequeName { get; set; }
Upvotes: 3
Reputation:
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:
Upvotes: 4
Reputation: 717
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; }
Upvotes: 4
Reputation: 985
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.
Upvotes: 3