我理解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.
*/
}
}
}
答案 0 :(得分:150)
首先,感谢@ 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()
将添加到结果集合中。如果验证失败,则不会向结果集合添加任何内容,这表示成功。
进行验证:
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()
方法处理条件验证。
答案 1 :(得分:76)
引自Jeff Handley's Blog Post on Validation Objects and Properties with Validator:
验证对象时, 以下过程适用于 Validator.ValidateObject:
- 验证属性级属性
- 如果任何验证器无效,则中止验证返回 失败(S)
- 验证对象级属性
- 如果任何验证器无效,则中止验证返回 失败(S)
- 如果在桌面框架和对象上实现 IValidatableObject,然后调用它 验证方法并返回任何方法 失败(S)
醇>
这表明您尝试做的事情不会开箱即用,因为验证将在步骤#2中止。您可以尝试创建从内置属性继承的属性,并在执行正常验证之前专门检查是否存在已启用的属性(通过接口)。或者,您可以将所有逻辑用于验证Validate
方法中的实体。
答案 2 :(得分:31)
只是添加几点:
因为Validate()
方法签名返回IEnumerable<>
,所以yield return
可以用于懒惰地生成结果 - 如果某些验证检查是IO或CPU密集型的话,这是有益的。< / p>
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
条目,如下所示(如果您在custom model binder中进行验证,这可能很有用) :
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)));
}
答案 3 :(得分:3)
我为验证实现了一般用法抽象类
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;
}
}
}
答案 4 :(得分:0)
接受答案的问题是它现在依赖于调用者来正确验证对象。我要么删除RangeAttribute并在Validate方法中进行范围验证,要么创建一个自定义属性子类化RangeAttribute,它将所需属性的名称作为构造函数的参数。
例如:
components = out.eval_components(x=x_data)
for model_name, model_value in components.items():
plt.plot(x_data, model_value)
# or more simply, if you prefer:
plt.plot(x_data, components['g0_'])
plt.plot(x_data, components['g1_'])
...
答案 5 :(得分:0)
我喜欢cocogza's answer,除了调用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;
}
}
这样效果更好!它不会崩溃并产生一个很好的错误消息。希望这有助于某人!
答案 6 :(得分:0)
我不喜欢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
{