I read on some post, but cant find it now that in MVC 3 it was not really needed to create a Validator, only the Attribute. Is this true? I do say I find it confusing that the attribute has the IClientValidatable on it. So what does the DataAnnotationsModelValidator class do if the annotation has the client side script name (IClientValidatable), and the ability to validate (ValidationAttribute IsValid)?
It would be really nice if I didnt have to register the Attribute with the Validator in the global. Can this be done? Did I read some bad advise?
EDIT: Interestingly enough I just tested it by excluding the validator, put all the logic in IsValid and it works great. I guess the only thing that might be missing would be the controller context, but I am not sure that is useful in validation. The IsValid has ValidationContext which has ServiceContainer if I needed a service. Any real disadvantage I am not picking up on here?
EDIT 2: I will start with a validator from this example: http://blogs.msdn.com/b/simonince/archive/2010/06/04/conditional-validation-in-mvc.aspx
The Attribute:
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule modelClientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "requiredifattribute"
};
modelClientValidationRule.ValidationParameters.Add("requiredifattribute", DependentProperty);
yield return modelClientValidationRule;
}
}
The Validator:
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
var value = field.GetValue(container, null);
if ((value == null && Attribute.TargetValue == null) ||
(value.Equals(Attribute.TargetValue)))
{
if (!Attribute.IsValid(Metadata.Model))
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
With the current code above, I need to register in the Global.asax.cs file:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
But if I move everything into just the attribute, I dont have to register it:
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && TargetValue == null) ||
(dependentValue.Equals(TargetValue)))
{
if (!innerAttribute.IsValid(value))
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public System.Collections.Generic.IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule modelClientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "requiredifattribute"
};
modelClientValidationRule.ValidationParameters.Add("requiredifattribute", DependentProperty);
yield return modelClientValidationRule;
}
}
Is there an problem with the last bit of code replacing all of the other code? Why would I keep the validator class?