Пример валидации значений одних полей в зависимости от других.
Отсюда: https://gist.github.com/elizabeth-young/5875161
public enum Comparison { IsEqualTo, IsNotEqualTo } ////// Validation attribute for required value if another property is set /// [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class RequiredIfAttribute : ValidationAttribute, IClientValidatable { private const string DefaultErrorMessageFormatString = "The {0} field is required."; public string OtherProperty { get; private set; } public Comparison Comparison { get; private set; } public Object Value { get; private set; } public RequiredIfAttribute(string otherProperty, Comparison comparison, object value) { if (string.IsNullOrEmpty(otherProperty)) { throw new ArgumentNullException("otherProperty"); } OtherProperty = otherProperty; Comparison = comparison; Value = value; } public override string FormatErrorMessage(string name) { return string.Format(ErrorMessageString, name, OtherProperty); } public bool Validate(object actualPropertyValue) { switch (Comparison) { case Comparison.IsNotEqualTo: return actualPropertyValue == null ? Value != null : !actualPropertyValue.Equals(Value); default: return actualPropertyValue == null ? Value == null : actualPropertyValue.Equals(Value); } } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null) { try { var property = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty); var propertyValue = property.GetValue(validationContext.ObjectInstance, null); if (Validate(propertyValue)) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } catch (Exception e) { if (OtherProperty.Contains('_')) { var prop = OtherProperty.Split('_'); var property = validationContext.ObjectInstance.GetType().GetProperty(prop.Last()); var propertyValue = property.GetValue(validationContext.ObjectInstance, null); if (!Validate(propertyValue)) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } } } return ValidationResult.Success; } public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { return new[] { new ModelClientValidationRequiredIfRule( FormatErrorMessage(metadata.GetDisplayName()), OtherProperty, Comparison, Value, metadata, context) }; } } public class ModelClientValidationRequiredIfRule : ModelClientValidationRule { public ModelClientValidationRequiredIfRule(string errorMessage, string otherProperty, Comparison comparison, object value, ModelMetadata metadata, ControllerContext context) { ErrorMessage = errorMessage; ValidationType = "requiredif"; ValidationParameters.Add("other", BuildDependentPropertyId(metadata, context as ViewContext, otherProperty)); ValidationParameters.Add("comp", comparison.ToString().ToLower()); ValidationParameters.Add("value", value.ToString().ToLower()); } private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext, string otherProperty) { return QualifyFieldId(metadata, otherProperty, viewContext); } protected string QualifyFieldId(ModelMetadata metadata, string fieldId, ViewContext viewContext) { // build the ID of the property string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(fieldId); // unfortunately this will have the name of the current field appended to the beginning, // because the TemplateInfo's context has had this fieldname appended to it. Instead, we // want to get the context as though it was one level higher (i.e. outside the current property, // which is the containing object (our Person), and hence the same level as the dependent property. var thisField = metadata.PropertyName + "_"; if (depProp.StartsWith(thisField)) // strip it off again depProp = depProp.Substring(thisField.Length); else if (depProp.Contains(thisField)) { depProp = depProp.Remove(depProp.IndexOf(thisField), thisField.Length); } return depProp; } }