Как использовать IValidatableObject?

Я понимаю, что IValidatableObject используется для проверки объекта таким образом, чтобы мы сравнивали свойства друг с другом.

Мне бы хотелось иметь атрибуты для проверки отдельных свойств, но я хочу игнорировать ошибки некоторых свойств в некоторых случаях.

Я пытаюсь использовать его неправильно в приведенном ниже случае? Если нет, то как это реализовать?

 public class ValidateMe : IValidatableObject { [Required] public bool Enable { get; set; } [Range(1, 5)] public int Prop1 { get; set; } [Range(1, 5)] public int Prop2 { get; set; } public IEnumerable Validate(ValidationContext validationContext) { if (!this.Enable) { /* Return valid result here. * I don't care if Prop1 and Prop2 are out of range * if the whole object is not "enabled" */ } else { /* Check if Prop1 and Prop2 meet their range requirements here * and return accordingly. */ } } } 

Во-первых, спасибо @ paper1337 за то, что указали мне нужные ресурсы … Я не зарегистрирован, поэтому я не могу проголосовать за него, пожалуйста, сделайте это, если кто-нибудь еще прочтет это.

Вот как выполнить то, что я пытался сделать.

Подтверждаемый class:

 public class ValidateMe : IValidatableObject { [Required] public bool Enable { get; set; } [Range(1, 5)] public int Prop1 { get; set; } [Range(1, 5)] public int Prop2 { get; set; } public IEnumerable Validate(ValidationContext validationContext) { var results = new List(); if (this.Enable) { Validator.TryValidateProperty(this.Prop1, new ValidationContext(this, null, null) { MemberName = "Prop1" }, results); Validator.TryValidateProperty(this.Prop2, new ValidationContext(this, null, null) { MemberName = "Prop2" }, results); // some other random test if (this.Prop1 > this.Prop2) { results.Add(new ValidationResult("Prop1 must be larger than Prop2")); } } return results; } } 

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

Выполнение проверки:

  public void DoValidation() { var toValidate = new ValidateMe() { Enable = true, Prop1 = 1, Prop2 = 2 }; bool validateAllProperties = false; var results = new List(); bool isValid = Validator.TryValidateObject( toValidate, new ValidationContext(toValidate, null, null), results, validateAllProperties); } 

Важно установить validateAllProperties на false, чтобы этот метод работал. Когда validateAllProperties является ложным, validateAllProperties только свойства с атрибутом [Required] . Это позволяет IValidatableObject.Validate() обрабатывать условные проверки.

Цитата из сообщения блога Джеффа Хандли о проверке объектов и свойств с помощью валидатора :

При проверке объекта в Validator применяется следующий процесс: ValidateObject:

  1. Проверка атрибутов уровня свойства
  2. Если какие-либо валидаторы недействительны, прекратите проверку, возвращая ошибку (-ы)
  3. Проверка атрибутов уровня объекта
  4. Если какие-либо валидаторы недействительны, прекратите проверку, возвращая ошибку (-ы)
  5. Если на рамочной платформе рабочего стола и объект реализует объект IValidatableObject, тогда вызовите его метод Validate и верните все отказы (ы)

Это указывает на то, что то, что вы пытаетесь сделать, не будет работать из коробки, потому что проверка будет отменена на шаге 2. Вы можете попытаться создать атрибуты, которые наследуются от встроенных, и специально проверить наличие включенного свойства (через интерфейс) перед выполнением их обычной проверки. В качестве альтернативы вы можете поместить всю логику для проверки объекта в методе Validate .

Просто добавьте пару моментов:

Поскольку сигнатура метода Validate() возвращает IEnumerable<> , это yield return , который может использоваться для ленивого генерации результатов – это полезно, если некоторые проверки проверки являются интенсивностью ввода-вывода или процессора.

 public IEnumerable Validate(ValidationContext validationContext) { if (this.Enable) { // ... if (this.Prop1 > this.Prop2) { yield return new ValidationResult("Prop1 must be larger than Prop2"); } 

Кроме того, если вы используете MVC ModelState , вы можете преобразовать ошибки результатов проверки в записи ModelState следующим образом (это может быть полезно, если вы выполняете проверку в настраиваемом связующем устройстве ):

 var resultsGroupedByMembers = validationResults .SelectMany(vr => vr.MemberNames .Select(mn => new { MemberName = mn ?? "", Error = vr.ErrorMessage })) .GroupBy(x => x.MemberName); foreach (var member in resultsGroupedByMembers) { ModelState.AddModelError( member.Key, string.Join(". ", member.Select(m => m.Error))); } 

Я применил абстрактный class использования для проверки

 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace App.Abstractions { [Serializable] abstract public class AEntity { public int Id { get; set; } public IEnumerable Validate() { var vResults = new List(); var vc = new ValidationContext( instance: this, serviceProvider: null, items: null); var isValid = Validator.TryValidateObject( instance: vc.ObjectInstance, validationContext: vc, validationResults: vResults, validateAllProperties: true); /* if (true) { yield return new ValidationResult("Custom Validation","A Property Name string (optional)"); } */ if (!isValid) { foreach (var validationResult in vResults) { yield return validationResult; } } yield break; } } } 

Проблема с принятым ответом заключается в том, что теперь это зависит от того, что вызывающий объект правильно проверен. Я бы либо удалил RangeAttribute, либо проверил валидацию диапазона внутри метода Validate, или я бы создал настраиваемый атрибут подclassа RangeAttribute, который принимает имя требуемого свойства в качестве аргумента в конструкторе.

Например:

 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] class RangeIfTrueAttribute : RangeAttribute { private readonly string _NameOfBoolProp; public RangeIfTrueAttribute(string nameOfBoolProp, int min, int max) : base(min, max) { _NameOfBoolProp = nameOfBoolProp; } public RangeIfTrueAttribute(string nameOfBoolProp, double min, double max) : base(min, max) { _NameOfBoolProp = nameOfBoolProp; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var property = validationContext.ObjectType.GetProperty(_NameOfBoolProp); if (property == null) return new ValidationResult($"{_NameOfBoolProp} not found"); var boolVal = property.GetValue(validationContext.ObjectInstance, null); if (boolVal == null || boolVal.GetType() != typeof(bool)) return new ValidationResult($"{_NameOfBoolProp} not boolean"); if ((bool)boolVal) { return base.IsValid(value, validationContext); } return null; } } 

Мне понравился ответ cocogza, за исключением того, что вызов base.IsValid привел к исключению переполнения стека, поскольку он снова и снова вводит метод IsValid. Поэтому я изменил его для определенного типа проверки, в моем случае это был адрес электронной почты.

 [AttributeUsage(AttributeTargets.Property)] class ValidEmailAddressIfTrueAttribute : ValidationAttribute { private readonly string _nameOfBoolProp; public ValidEmailAddressIfTrueAttribute(string nameOfBoolProp) { _nameOfBoolProp = nameOfBoolProp; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (validationContext == null) { return null; } var property = validationContext.ObjectType.GetProperty(_nameOfBoolProp); if (property == null) { return new ValidationResult($"{_nameOfBoolProp} not found"); } var boolVal = property.GetValue(validationContext.ObjectInstance, null); if (boolVal == null || boolVal.GetType() != typeof(bool)) { return new ValidationResult($"{_nameOfBoolProp} not boolean"); } if ((bool)boolVal) { var attribute = new EmailAddressAttribute {ErrorMessage = $"{value} is not a valid e-mail address."}; return attribute.GetValidationResult(value, validationContext); } return null; } } 

Это работает намного лучше! Он не сбой и создает приятное сообщение об ошибке. Надеюсь, это поможет кому-то!

Давайте будем гением компьютера.