Validation of properties that require the values of other properties

六眼飞鱼酱① 提交于 2021-01-29 03:36:56

问题


So I have checked out this answer ASP:NET MVC 4 dynamic validation of a property depending of the current value of another property and it does not cover the issue I am having.

I am using server side validation. I have a requirement that...

A value is only required if another property is specified

Issue

MVC binds each property and calls each validator on that property as it binds them. If I am dependent on multiple properties being set when I check validationContext.ObjectInstance.[MY_DEPENDENT_PROPERTY] there is a possibility that those dependent properties have not been bound yet.

What I need is a validation attribute that validates after binding - if that even exists.


So here is a simple example to explain my situation (not intended to be executed as it will more than likely be fine since the issue has to do with binding order)

My model

public class Address
{
    [Required]
    public string ResidentialAddress { get; set; }

    public bool PostalIsTheSameAsResidential { get; set; }

    // will only be required if PostalIsTheSameAsResidential is false.
    // see the static method below and RequiredIfAttribute
    [RequiredIf(typeof(Address), nameof(PostalRequiredIfNotSameAsResidential)]
    public string PostalAddress { get; set; }

    public static bool PostalRequiredIfNotSameAsResidential(Address model)
    {
        return !model.PostalIsTheSameAsResidential;
    }
}

My validator

Essentially what happens here is it calls the static method on the model to see whether it should validate.

public sealed class RequiredIfAttribute : RequiredAttribute
{
    private readonly MethodInfo _validationMethod;
    public override bool RequiresValidationContext => true;

    public RequiredIfAttribute(Type type, string methodName)
    {
        this._validationMethod = type.GetMethod(methodName);
        if (this._validationMethod == null)
        {
            throw new MethodAccessException($"The validation method '{methodName}' does not exist on type '{type}");
        }
    }

    public override bool IsValid(object value)
    {
        throw new NotSupportedException();
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        ValidationResult result = ValidationResult.Success;

        var parameters = this._validationMethod.GetParameters();
        var returnType = this._validationMethod.ReturnType;

        if (returnType == typeof(bool) && parameters.Length == 1 && parameters[0].ParameterType == validationContext.ObjectType)
        {
            if ((bool)_validationMethod.Invoke(null, new object[] { validationContext.ObjectInstance }))
            {
                if (!base.IsValid(value))
                {
                    string[] memberNames;
                    if (validationContext.MemberName == null)
                    {
                        memberNames = null;
                    }
                    else
                    {
                        memberNames = new string[1];
                        memberNames[0] = validationContext.MemberName;
                    }
                    result = new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), memberNames);
                }
            }
            return result;
        }

        var expectedFuncType = typeof(Func<,>).MakeGenericType(validationContext.ObjectType, typeof(bool));
        throw new MethodAccessException($"The validation method '{this._validationMethod}' does not have the correct definition. Expected '{expectedFuncType}'");
    }
}

回答1:


So this issue that I was having was that I was inheriting from the RequiredAttribute. Internally MVC handles this attribute differently to everything else.

When the Model Binder is looping through the properties, it gets the RequiredAttributes and executes them at the same time...

// System.Web.Mvc.DefaultModelBinder.SetProperty
....
    ModelValidator modelValidator = (from v in ModelValidatorProviders.Providers.GetValidators(modelMetadata, controllerContext)
        where v.IsRequired
        select v).FirstOrDefault<ModelValidator>();
        if (modelValidator != null)
        {
            foreach (ModelValidationResult current in modelValidator.Validate(bindingContext.Model))
            {
                bindingContext.ModelState.AddModelError(key, current.Message);
            }
        }
....

That v.IsRequired actually resolves to a line that tests if the current attribute is a RequiredAttribute and will validate it there, in the current, incomplete model state.

By inheriting from ValidationAttribute it ran the validations after the model had been built and solved my issue.


Thanks to @StephenMuecke for prompting me with this.



来源:https://stackoverflow.com/questions/40478695/validation-of-properties-that-require-the-values-of-other-properties

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!