Проблема - порядок связывания модели
Это, к сожалению, стандартное поведение Validator.TryValidateObject
, которое
не рекурсивно проверяет значения свойствобъект
Как указано в статье Джеффа Хэндли о Проверка объекта и свойств с помощью валидатора , по умолчанию валидатор будет проверять в следующем порядке:
- Атрибуты уровня свойства
- Атрибуты уровня объекта
- Реализация уровня модели
IValidatableObject
Проблема в том, что на каждом этапе пути ...
Если какие-либо валидаторы недействительны, Validator.ValidateObject
прервет валидацию и выдаст ошибку (и)
Проблема - поля связующего элемента модели
ДругойВозможная проблема заключается в том, что связыватель модели будет выполнять проверку только на тех объектах, которые он решил связать.Например, если вы не предоставляете входные данные для полей внутри сложных типов в вашей модели, связывателю модели вообще не нужно будет проверять эти свойства, поскольку он не вызвал конструктор для этих объектов.Согласно замечательной статье Брэда Уилсона о Проверка входных данных по сравнению с проверкой модели в ASP.NET MVC :
Причина, по которой мы не "рекурсивно" погружаемся в объект Address, заключается в том, чтов форме не было ничего, что ограничивало бы какие-либо значения внутри Address.
Решение - Проверка объекта одновременно со свойствами
Один из способов решения этой проблемы - преобразование объекта.проверки уровня к проверке уровня свойства путем добавления настраиваемого атрибута проверки к свойству, которое будет возвращаться с результатом проверки самого объекта.
Статья Джоша Кэрролла о Рекурсивная проверка с использованием DataAnnotations обеспечивает реализациюодной такой стратегии (первоначально в этот вопрос ).Если мы хотим проверить сложный тип (например, Address), мы можем добавить в свойство пользовательский атрибут ValidateObject
, чтобы он оценивался на первом шаге
public class Person {
[Required]
public String Name { get; set; }
[Required, ValidateObject]
public Address Address { get; set; }
}
. Вам нужно будет добавитьследующая реализация ValidateObjectAttribute :
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(<em>field</em>, <em>error</em>)
.Как указано в Как заставить 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);
}
}
Также , если вы хотите более элегантныйВ этом случае вы можете написать код один раз, предоставив собственную реализацию связывания модели в Application_Start () с ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
.Есть хорошие реализации здесь и здесь