我如何使用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<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.
*/
}
}
}
150087 次浏览

引用自Jeff Handley关于Validator的验证对象和属性的博客文章:

当验证一个对象时, 以下流程应用于 验证器。ValidateObject: < / p >

  1. 验证属性级属性
  2. 如果任何验证器无效,则终止验证返回 失败(s) < / em > < /李>
  3. 验证对象级属性
  4. 如果任何验证器无效,则终止验证返回 失败(s) < / em > < /李>
  5. 如果在桌面框架和对象实现 IValidatableObject,然后调用它 验证方法并返回any 李失败(s) < / >

这表明您试图执行的操作将无法开箱即用,因为验证将在步骤#2终止。您可以尝试创建从内置属性继承的属性,并在执行正常验证之前检查启用的属性是否存在(通过接口)。或者,你可以把验证实体的所有逻辑都放在Validate方法中。

你也可以看看Validator在这里的确切实现

首先,感谢@paper1337为我指出了正确的资源……我没有注册,所以我不能投票给他,如果有人读到这篇文章,请这样做。

以下是如何完成我试图做的事情。

Validatable类:

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()将添加到结果集合中。如果没有失败的验证,则不会向结果集合添加任何东西,这表明验证成功。

进行验证:

    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()方法处理条件验证。

再补充一点:

因为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)));
}

我实现了一个用于验证的通用抽象类

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




}
}

可接受的答案的问题是,它现在取决于调用者是否正确验证对象。我要么删除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;
}
}

我喜欢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;
}
}

这样效果更好!它不会崩溃,并产生一个漂亮的错误消息。希望这能帮助到一些人!

我不喜欢iValidate的事情是它似乎只在所有其他验证之后运行 此外,至少在我们的站点中,它会在保存尝试期间再次运行。我建议您简单地创建一个函数,并将所有验证代码放在其中。另外,对于网站,您可以在模型创建后在控制器中进行“特殊”验证。例子:< / p >
 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
{

使用IValidatableObject或属性级验证(属性)实现验证逻辑,而不是像这样使用System.ComponentModel.DataAnnotations.Validator类

var validationContext = new ValidationContext(model,, null, null);
var validations = new Collection<ValidationResult>();
Validator.TryValidaObject(model, validationContext, validations, true)

任何错误都应该出现在验证集合中(ErrorMessage属性不应该为空)。

https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validator?view=net-6.0