我如何使用IValidatableObject?
我明白, IValidatableObject
是用来validation一个对象的方式,让我们一个比较属性相互。
我仍然希望有属性来validation各个属性,但是我想在某些情况下忽略某些属性的失败。
我是否在下面的情况下错误地使用它? 如果不是我如何执行这个?
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<ValidationResult> 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指向我正确的资源…我没有注册,所以我不能投票给他,如果有人读这个,请这样做。
以下是如何完成我正在尝试做的事情。
可validation的类:
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<ValidationResult> Validate(ValidationContext validationContext) { var results = new List<ValidationResult>(); 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()
失败,则使用Validator.TryValidateProperty()
将添加到结果集合中。 如果没有失败的validation,那么没有任何东西会被添加到结果集合中,这是成功的指示。
做validation:
public void DoValidation() { var toValidate = new ValidateMe() { Enable = true, Prop1 = 1, Prop2 = 2 }; bool validateAllProperties = false; var results = new List<ValidationResult>(); bool isValid = Validator.TryValidateObject( toValidate, new ValidationContext(toValidate, null, null), results, validateAllProperties); }
为使此方法正常工作,将validateAllProperties
设置为false非常重要。 当validateAllProperties
为false时,仅检查具有[Required]
属性的属性。 这允许IValidatableObject.Validate()
方法处理条件validation。
引用来自Jeff Handley的博客postvalidation对象和属性与validation :
validation对象时,以下过程应用于Validator.ValidateObject:
- validation属性级属性
- 如果任何validation器无效,则中止validation返回失败(s)
- validation对象级别的属性
- 如果任何validation器无效,则中止validation返回失败(s)
- 如果在桌面框架和对象上实现了IValidatableObject,那么调用它的Validate方法并返回任何失败(s)
这表明您正在尝试执行的操作不能即时运行,因为validation将在步骤#2中止。 您可以尝试创build从内置inheritance的属性,并在执行常规validation之前检查是否存在已启用的属性(通过接口)。 或者,您可以将所有用于validation实体的逻辑放在Validate
方法中。
只需要添加几点:
由于Validate()
方法签名返回IEnumerable<>
,因此可以使用yield return
来延迟生成结果 – 如果某些validation检查是IO或CPU密集型,这将是有益的。
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (this.Enable) { // ... if (this.Prop1 > this.Prop2) { yield return new ValidationResult("Prop1 must be larger than Prop2"); }
另外,如果您使用的是MVC ModelState
,则可以将validation结果失败转换为ModelState
条目(如果您正在自定义模型联编程序中进行validation,这可能很有用):
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))); }
我实现了一个通用的抽象类进行validation
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<ValidationResult> Validate() { var vResults = new List<ValidationResult>(); 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; } } }
接受的答案的问题是,它现在取决于调用者的对象进行适当的validation。 我要么删除RangeAttribute,并在Validate方法内进行范围validation,要么创build一个自定义属性子类化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导致堆栈溢出exception,因为它会一次又一次地重新inputIsValid方法。 所以我修改它是为了一个特定types的validation,在我的情况下是一个电子邮件地址。
[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; } }
这效果更好! 它不会崩溃,并产生一个很好的错误消息。 希望这可以帮助别人!