Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 2 vs MVC 3 custom validation attributes using DataAnnotationsModelValidatorProvider.RegisterAdapter

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?

like image 763
CrazyDart Avatar asked Jun 27 '11 15:06

CrazyDart


1 Answers

CrazyDart,

The IClientValidatable interface was added in MVC3.

Your second example shows a valid use of this new interface. You are correct that it does not have to be registered, and it will provide the necessary client side rules for validation, as well as doing the necessary server side validation.

Go ahead, enjoy it.

counsellorben

like image 155
counsellorben Avatar answered Nov 16 '22 02:11

counsellorben