问题 - 模型活页夹订单
不幸的是,这是标准行为Validator.TryValidateObject which
不递归地验证对象的属性值
正如杰夫·汉德利 (Jeff Handley) 的文章中指出的使用验证器验证对象和属性,默认情况下,验证器将按顺序验证:
- 属性级属性
- 对象级属性
- 模型级实现
IValidatableObject
问题是,每一步......
如果任何验证器无效,Validator.ValidateObject
将中止验证并返回失败
问题 - 模型绑定器字段
另一个可能的问题是模型绑定器只会对其决定绑定的对象运行验证。例如,如果您不为模型上的复杂类型内的字段提供输入,则模型绑定器根本不需要检查这些属性,因为它没有调用这些对象的构造函数。根据 Brad Wilson 的精彩文章ASP.NET MVC 中的输入验证与模型验证:
我们不递归地“深入”Address 对象的原因是,表单中没有任何内容绑定Address 内部的任何值。
解决方案 - 在验证属性的同时验证对象
解决此问题的一种方法是将对象级验证转换为属性级验证,方法是向属性添加自定义验证属性,该属性将随对象本身的验证结果一起返回。
乔什·卡罗尔的文章使用数据注释的递归验证提供了一个这样的策略的实现(最初在这个问题)。如果我们想验证复杂类型(如地址),我们可以添加自定义ValidateObject
属性的属性,因此它在第一步进行评估
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
您需要添加以下内容验证对象属性执行:
public class ValidateObjectAttribute: ValidationAttribute {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0) {
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
public class CompositeValidationResult: ValidationResult {
private readonly List<ValidationResult> _results = new List<ValidationResult>();
public IEnumerable<ValidationResult> Results {
get {
return _results;
}
}
public CompositeValidationResult(string errorMessage) : base(errorMessage) {}
public CompositeValidationResult(string errorMessage, IEnumerable<string> memberNames) : base(errorMessage, memberNames) {}
protected CompositeValidationResult(ValidationResult validationResult) : base(validationResult) {}
public void AddResult(ValidationResult validationResult) {
_results.Add(validationResult);
}
}
解决方案 - 在验证属性的同时验证模型
对于实现的对象IValidatableObject
,当我们检查 ModelState 时,我们还可以在返回错误列表之前检查模型本身是否有效。我们可以通过调用添加任何我们想要的错误ModelState.AddModelError(field, error)
。如指定如何强制 MVC 验证 IValidatableObject,我们可以这样做:
[HttpPost]
public ActionResult Create(Model model) {
if (!ModelState.IsValid) {
var errors = model.Validate(new ValidationContext(model, null, null));
foreach (var error in errors)
foreach (var memberName in error.MemberNames)
ModelState.AddModelError(memberName, error.ErrorMessage);
return View(post);
}
}
Also,如果您想要更优雅的解决方案,您可以通过在 Application_Start() 中提供您自己的自定义模型绑定器实现来编写一次代码ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
。有很好的实现here and here