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<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.
*/
}
}
}
#1 楼
首先,感谢@ paper1337为我提供了正确的资源...我没有注册,所以我无法投票给他,如果有人读过这个,请这样做。这里是完成我想要做的事情。
可验证的类: 。如果没有失败的验证,那么将不向结果集合添加任何表示成功的指示。
进行验证:
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()
设置为false才能使此方法起作用。当validateAllProperties
为false时,仅检查具有validateAllProperties
属性的属性。这使[Required]
方法可以处理条件验证。#2 楼
引用Jeff Handley的博客文章有关使用Validator进行验证的对象和属性的引用:验证对象时,在
Validator.ValidateObject:
验证属性级属性
如果任何验证器无效,则中止验证并返回
失败
验证对象级属性
如果任何验证器无效,中止验证,返回
失败
如果在桌面框架上并且对象实现了
IValidatableObject,则调用其
Validate方法并返回任何
失败)
这表明您尝试执行的操作无法立即使用,因为验证将在步骤2中终止。您可以尝试创建从内置属性继承的属性,并在执行常规验证之前(通过接口)专门检查已启用属性的存在。另外,您也可以将所有用于验证实体的逻辑放在
Validate
方法中。您还可以在此处查看
Validator
类的确切实现#3 楼
只需添加以下几点:由于Validate()
方法签名返回IEnumerable<>
,因此yield return
可用于延迟生成结果-如果某些验证检查是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
,则可以将验证结果失败转换为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)));
}
评论
好一个!在Validate方法中使用属性和反射是否值得?
– Schalk
2015年2月25日在12:25
#4 楼
我实现了用于验证的通用抽象类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;
}
}
}
评论
我喜欢使用命名参数的这种风格,使代码更易于阅读。
– drizin
17年8月31日在18:49
#5 楼
可接受答案的问题在于,它现在依赖于调用者来正确验证对象。我要么删除RangeAttribute并在Validate方法内进行范围验证,要么创建一个自定义属性子类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;
}
}
#6 楼
除了调用base.IsValid导致堆栈溢出异常外,我喜欢cocogza的回答,因为它将一次又一次地重新输入IsValid方法。因此,我将其修改为用于特定类型的验证,在我的情况下,它是针对电子邮件地址的。它不会崩溃,并且会产生一个不错的错误消息。希望这对某人有帮助!#7 楼
我不喜欢iValidate的地方在于,它似乎只能在所有其他验证之后运行。我建议您只需创建一个函数,然后将所有验证代码放入其中。或者,对于网站,可以在创建模型后在控制器中进行“特殊”验证。示例: public ActionResult Update([DataSourceRequest] DataSourceRequest request, [Bind(Exclude = "Terminal")] Driver driver)
{
if (db.Drivers.Where(m => m.IDNumber == driver.IDNumber && m.ID != driver.ID).Any())
{
ModelState.AddModelError("Update", string.Format("ID # '{0}' is already in use", driver.IDNumber));
}
if (db.Drivers.Where(d => d.CarrierID == driver.CarrierID
&& d.FirstName.Equals(driver.FirstName, StringComparison.CurrentCultureIgnoreCase)
&& d.LastName.Equals(driver.LastName, StringComparison.CurrentCultureIgnoreCase)
&& (driver.ID == 0 || d.ID != driver.ID)).Any())
{
ModelState.AddModelError("Update", "Driver already exists for this carrier");
}
if (ModelState.IsValid)
{
try
{
评论
我想不出一个我会用它的场景。您能举一个例子说明如何使用它吗?
– Stefan Vasiljevic
16-2-25在15:39
如果您的表中有跟踪列(例如创建它的用户)。它在数据库中是必需的,但是您可以在上下文中加入SaveChanges来填充它(从而使开发人员不必记住明确地对其进行设置)。当然,您将在保存之前进行验证。因此,您无需将“ creator”列标记为必填项,而是针对所有其他列/属性进行验证。
–金属凤凰
16年7月13日在18:15
该解决方案的问题在于,现在您依赖于调用者来正确验证对象。
– cocogza
17年2月28日在22:06
为了增强此答案,可以使用反射来查找具有验证属性的所有属性,然后调用TryValidateProperty。
–Paul Chernoch
17年10月7日在16:03