To obtain client-side validation, you need to pass the values of the “other properties” to the ModelClientValidationRule using the .Add() method of the ValidationParameters rule property, and then write client-side scripts to add the rules to $.validator .
But first, there are a few other issues related to your attribute. First, you should run the foreach only if the value of the property to which you applied the attribute is null . Secondly, returning a ValidationResult if one of the “other properties” does not exist is confusing and pointless for the user, and you should just ignore it.
The attribute code should be (note that I changed the attribute name)
public class RequiredIfAnyAttribute : ValidationAttribute, IClientValidatable { private readonly string[] _otherProperties; private const string _DefaultErrorMessage = "The {0} field is required"; public RequiredIfAnyAttribute(params string[] otherProperties) { if (otherProperties.Length == 0) // would not make sense { throw new ArgumentException("At least one other property name must be provided"); } _otherProperties = otherProperties; ErrorMessage = _DefaultErrorMessage; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null) // no point checking if it has a value { foreach (string property in _otherProperties) { var propertyName = validationContext.ObjectType.GetProperty(property); if (propertyName == null) { continue; } var propertyValue = propertyName.GetValue(validationContext.ObjectInstance, null); if (propertyValue != null) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } } return ValidationResult.Success; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule { ValidationType = "requiredifany", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), }; / pass a comma separated list of the other propeties rule.ValidationParameters.Add("otherproperties", string.Join(",", _otherProperties)); yield return rule; } }
Then the scripts will be
sandtrapValidation = { getDependentElement: function (validationElement, dependentProperty) { var dependentElement = $('#' + dependentProperty); if (dependentElement.length === 1) { return dependentElement; } var name = validationElement.name; var index = name.lastIndexOf(".") + 1; var id = (name.substr(0, index) + dependentProperty).replace(/[\.\[\]]/g, "_"); dependentElement = $('#' + id); if (dependentElement.length === 1) { return dependentElement; } // Try using the name attribute name = (name.substr(0, index) + dependentProperty); dependentElement = $('[name="' + name + '"]'); if (dependentElement.length > 0) { return dependentElement.first(); } return null; } } $.validator.unobtrusive.adapters.add("requiredifany", ["otherproperties"], function (options) { var element = options.element; var otherNames = options.params.otherproperties.split(','); var otherProperties = []; $.each(otherNames, function (index, item) { otherProperties.push(sandtrapValidation.getDependentElement(element, item)) }); options.rules['requiredifany'] = { otherproperties: otherProperties }; options.messages['requiredifany'] = options.message; }); $.validator.addMethod("requiredifany", function (value, element, params) { if ($(element).val() != '') { // The element has a value so its OK return true; } var isValid = true; $.each(params.otherproperties, function (index, item) { if ($(this).val() != '') { isValid = false; } }); return isValid; });