C# 如何使用IValidatableObject?
我知道,C# 如何使用IValidatableObject?,c#,asp.net,ivalidatableobject,C#,Asp.net,Ivalidatableobject,我知道,IValidatableObject用于验证对象,使人们能够相互比较属性 我仍然希望使用属性来验证单个属性,但我希望在某些情况下忽略某些属性的失败 我是否试图在下面的案例中错误地使用它?如果没有,我该如何实现这一点 public class ValidateMe : IValidatableObject { [Required] public bool Enable { get; set; } [Range(1, 5)] public int Prop1
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.
*/
}
}
}
public类ValidateMe:IValidatableObject
{
[必需]
公共bool启用{get;set;}
[范围(1,5)]
公共int Prop1{get;set;}
[范围(1,5)]
公共int Prop2{get;set;}
公共IEnumerable验证(ValidationContext ValidationContext)
{
如果(!this.Enable)
{
/*在此处返回有效结果。
*我不在乎Prop1和Prop2是否超出范围
*如果整个对象未“启用”
*/
}
其他的
{
/*在此检查Prop1和Prop2是否满足其范围要求
*并相应地返回。
*/
}
}
}
引用自:
验证对象时
以下过程应用于
Validator.ValidateObject:
Validate
方法中
您还可以先看一下
验证程序
类的具体实现,感谢@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;
}
}
将
validateAllProperties
设置为false对于此方法的工作非常重要。当validateAllProperties
为false时,仅检查具有[必需]
属性的属性。这允许IValidatableObject.Validate()
方法处理条件验证。只需添加几点:
因为Validate()
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.Enable)
{
// ...
if (this.Prop1 > this.Prop2)
{
yield return new ValidationResult("Prop1 must be larger than Prop2");
}
我实现了一个用于验证的通用抽象类
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;
}
}
}
使用系统;
使用System.Collections.Generic;
使用System.ComponentModel.DataAnnotations;
名称空间App.Abstractions
{
[可序列化]
抽象公共类实体
{
公共int Id{get;set;}
公共IEnumerable验证()
{
var vResults=新列表();
var vc=新的ValidationContext(
实例:这个,,
服务提供者:null,
项目:空);
var isValid=Validator.TryValidateObject(
实例:vc.ObjectInstance,
validationContext:vc,
验证结果:vResults,
validateAllProperties:true);
/*
如果(真)
{
返回新的ValidationResult(“自定义验证”,“属性名称字符串(可选)”);
}
*/
如果(!isValid)
{
foreach(vResults中的var validationResult)
{
收益率验证结果;
}
}
屈服断裂;
}
}
}
接受答案的问题是,现在要正确验证对象,它取决于调用者。我要么删除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;
}
}
除了调用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的一点是它似乎只在所有其他验证之后运行。
此外,至少在我们的站点中,它会在尝试保存时再次运行。我建议您只需创建一个函数并将所有验证代码放在其中。或者,对于网站,您可以在创建模型后在控制器中进行“特殊”验证。例如:
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
{
不错!在Validate方法中使用属性和反射值得吗?我想不出会在哪种情况下使用它。如果表中有跟踪列(例如创建跟踪列的用户),您可以给我一个使用此功能的示例。它在数据库中是必需的,但是您可以在上下文中插入SaveChanges来填充它(无需开发人员记住显式设置它)。当然,您需要在保存之前进行验证。因此,您没有按要求标记“creator”列,而是根据所有其他列/属性进行验证。此解决方案的问题是,现在您依赖调用者来正确验证对象。要增强此功能,请
[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;
}
}
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
{