Почему Validator.TryValidateObject не проверяет класс, если у меня есть атрибут проверки в свойстве?

Я создал настраиваемый атрибут ValidationAttribute, предназначенный для класса. Это правильно проверяется всякий раз, когда я пытаюсь вызвать Validator.TryValidateObject. Но когда у меня есть другой атрибут ValidationAttribute в свойствах внутри моего класса, результаты проверки не содержат результат проверки уровня класса.

Вот пример кода:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class IsHelloWorldAttribute : ValidationAttribute
{
    public object _typeId = new object();
    public string FirstProperty { get; set; }
    public string SecondProperty { get; set; }

    public IsHelloWorldAttribute(string firstProperty, string secondProperty)
    {
        this.FirstProperty = firstProperty;
        this.SecondProperty = secondProperty; 
    }

    public override bool IsValid(object value)
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
        string str1 = properties.Find(FirstProperty, true).GetValue(value) as string;
        string str2 = properties.Find(SecondProperty, true).GetValue(value) as string;

        if (string.Format("{0}{1}", str1,str2) == "HelloWorld")
            return true;
        return false;
    }

    public override object TypeId
    {
        get
        {
            return _typeId;
        }
    }
}

Вот код класса, который мне нужно проверить

[IsHelloWorld("Name", "Code", ErrorMessage="Is not Hello World")]
public class MyViewModel : BaseViewModel
{
    string name;
    string code;

    [Required]
    public string Name
    {
        get { return model.Name; }
        set
        {
            if (model.Name != value)
            {
                model.Name = value;
                base.RaisePropertyChanged(() => this.Name);
            }
        }
    }        

    public string Code
    {
        get { return code; }
        set
        {
            if (code != value)
            {
                code = value;
                base.RaisePropertyChanged(() => this.Code);
            }
        }
    }
}

Вот как я вызываю метод TryValidateObject:

            var validationContext = new ValidationContext(this, null, null);               
            var validationResults = new List<ValidationResult>();               
            Validator.TryValidateObject(this, validationContext, validationResults, true);

Теперь, если у меня есть атрибут [Required] в свойстве Name, и я попытался вызвать Validator.TryValidateObject, результат проверки будет только один, то есть результат обязательной проверки. Но когда я удалил атрибут [Required] из Name и оставил атрибут IsHellowWorld, а затем вызвал TryValidateObject, он даст мне один результат - результат HellowWorldValidation.

Что мне нужно сделать, так это получить всю валидацию на уровне класса и на уровне свойств. Могу ли я добиться этого без реализации собственного метода TryValidateObject?


person Lance    schedule 05.02.2014    source источник
comment
Проверка на уровне класса не сработает, если свойства недействительны - это ваш случай?   -  person Lanorkin    schedule 06.07.2014
comment
Хорошо, я понимаю. Спасибо Ланоркину.   -  person Lance    schedule 28.08.2015


Ответы (1)


Это связано с тем, что при обнаружении ошибок свойств происходит короткое замыкание проверки. Это имеет смысл, поскольку проверка на уровне класса может быть более дорогостоящей, что потенциально может включать обратные вызовы, вызовы других источников данных и т. Д.

Если была обнаружена ошибка свойства, то логика в ее нынешнем виде просто перестает работать.

В исходном коде System.ComponentModel.DataAnnotations.Validator:

public static bool TryValidateObject(object instance, ValidationContext validationContext, 
  ICollection<ValidationResult> validationResults, bool validateAllProperties)
{
  if (instance == null)
    throw new ArgumentNullException("instance");
  if (validationContext != null && instance != validationContext.ObjectInstance)
    throw new ArgumentException(DataAnnotationsResources.
    Validator_InstanceMustMatchValidationContextInstance, "instance");
  bool flag = true;
  bool breakOnFirstError = validationResults == null;
  foreach (Validator.ValidationError validationError in 
    Validator.GetObjectValidationErrors(instance, validationContext, 
    validateAllProperties, breakOnFirstError))
  {
    flag = false;
    if (validationResults != null)
      validationResults.Add(validationError.ValidationResult);
  }
  return flag;
}

Обратите внимание на вызов Validator.GetObjectValidationErrors, который, в свою очередь, определяется как:

private static IEnumerable<Validator.ValidationError> GetObjectValidationErrors(
  object instance, ValidationContext validationContext, 
  bool validateAllProperties, bool breakOnFirstError)
{
  if (instance == null)
    throw new ArgumentNullException("instance");
  if (validationContext == null)
    throw new ArgumentNullException("validationContext");
  List<Validator.ValidationError> list = new List<Validator.ValidationError>();

  //Check for property errors here
  list.AddRange(Validator.GetObjectPropertyValidationErrors(instance, validationContext,
    validateAllProperties, breakOnFirstError));

  // Short circuits here if any found
  if (Enumerable.Any<Validator.ValidationError>(
    (IEnumerable<Validator.ValidationError>) list))
    return (IEnumerable<Validator.ValidationError>) list;

  // Class level validation occurs below this point
  IEnumerable<ValidationAttribute> validationAttributes = 
    Validator._store.GetTypeValidationAttributes(validationContext);
  list.AddRange(Validator.GetValidationErrors(instance, validationContext, 
    validationAttributes, breakOnFirstError));
  if (Enumerable.Any<Validator.ValidationError>(
    (IEnumerable<Validator.ValidationError>) list))
    return (IEnumerable<Validator.ValidationError>) list;
  IValidatableObject validatableObject = instance as IValidatableObject;
  if (validatableObject != null)
  {
    foreach (ValidationResult validationResult in 
      Enumerable.Where<ValidationResult>(validatableObject.Validate(validationContext), 
      (Func<ValidationResult, bool>) (r => r != ValidationResult.Success)))
      list.Add(new Validator.ValidationError((ValidationAttribute) null, instance, 
        validationResult));
  }
  return (IEnumerable<Validator.ValidationError>) list;
}
person rism    schedule 11.08.2015
comment
Для меня это был флаг validateAllProperties. По умолчанию это false, что означает, что проверяются только обязательные атрибуты поля. Установка этого флага в значение true приведет к тому, что проверка также будет выполняться для других атрибутов DataAnnotation. - person chrsmrtn; 17.09.2020