Проверка свойств, для которых требуются значения других свойств

Итак, я проверил этот ответ of">Динамическая проверка ASP:NET MVC 4 свойства в зависимости от текущего значения другого свойства и не охватывает проблему, с которой я столкнулся.

Я использую проверку на стороне сервера. У меня есть требование, что...

Значение требуется, только если указано другое свойство

Проблема

MVC связывает каждое свойство и вызывает каждый валидатор для этого свойства по мере их связывания. Если я зависим от нескольких свойств, установленных при проверке validationContext.ObjectInstance.[MY_DEPENDENT_PROPERTY], есть вероятность, что эти зависимые свойства еще не привязаны.

Что мне нужно, так это атрибут проверки, который проверяет после привязки, если он вообще существует.


Итак, вот простой пример, объясняющий мою ситуацию (не предназначен для выполнения, поскольку он, скорее всего, будет в порядке, поскольку проблема связана с порядком привязки)

Моя модель

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;
    }
}

Мой валидатор

По сути, здесь происходит вызов статического метода модели, чтобы проверить, следует ли ее проверять.

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}'");
    }
}

person Michael Coxon    schedule 08.11.2016    source источник
comment
Есть ли какая-то причина, по которой вам не нужна проверка на стороне клиента, а также проверка на стороне сервера. Вы можете использовать foolproof [RequiredIfFalse("PostalIsTheSameAsResidential")] или аналогичный атрибут проверки (или вы можете легко написать свой собственный - Полное руководство по Проверка в ASP.NET MVC 3 — часть 2   -  person    schedule 08.11.2016
comment
Обратите также внимание, что вы должны наследовать от ValidationAttribute, а не RequiredAttribute   -  person    schedule 08.11.2016
comment
И ваши предположения о способе работы привязки и проверки неверны — параметр validationContext метода IsValid() содержит всю информацию о необходимых вам значениях свойств — var property = validationContext.ObjectInstance.GetType().GetProperty("PostalIsTheSameAsResidential "); var propertyValue = property.GetValue(validationContext.ObjectInstance, null); вернет значение вашего свойства bool.   -  person    schedule 08.11.2016
comment
@StephenMuecke Ваш второй комментарий был проблемой. Я опубликую запись в качестве ответа   -  person Michael Coxon    schedule 08.11.2016
comment
Вы должны принять к сведению и другие 2 (вы пишете в два раза больше кода, чем необходимо для этого)   -  person    schedule 08.11.2016
comment
@StephenMuecke Я не понимаю, почему вы думаете, что я пишу в два раза больше кода, чем нужно... ответ на ваш первый комментарий заключается в том, что мне нужно запустить код C# при проверке. Фактическая проверка намного, намного сложнее, чем пример, который я привел. Цель примера заключалась в том, чтобы его было легко читать и следовать ему. Ваш третий комментарий вообще не соответствует моему варианту использования, так как мне нужен полный объект, а не только одно свойство.   -  person Michael Coxon    schedule 08.11.2016
comment
Вы получаете «полный объект» (вы можете получить все свойства и значения из validationContext. Не уверен, что вы имеете в виду, ответ на ваш первый комментарий заключается в том, что мне нужно запустить код C# при проверке? Я просто комментировал, почему вы также не реализуете IClientValidatable и не получаете проверку на стороне клиента, а также проверку на стороне сервера.   -  person    schedule 08.11.2016


Ответы (1)


Итак, проблема, с которой я столкнулся, заключалась в том, что я наследовал от RequiredAttribute. Внутри MVC обрабатывает этот атрибут иначе, чем все остальное.

Когда средство связывания модели перебирает свойства, оно получает RequiredAttribute и выполняет их одновременно...

// 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);
            }
        }
....

Этот v.IsRequired на самом деле разрешается в строку, которая проверяет, является ли текущий атрибут RequiredAttribute, и проверяет его там, в текущем, неполном состоянии модели.

Унаследовав от ValidationAttribute, он выполнил проверки после того, как модель была построена, и решила мою проблему.


Спасибо @StephenMuecke за то, что подсказал мне это.

person Michael Coxon    schedule 08.11.2016