Проверка модели представления после привязки пользовательской модели

У меня есть модель представления, которая реализует IValidatableObject, которая содержит строку и коллекцию другой модели представления, примерно так:

public sealed class MainViewModel
{
    public string Name { get; set; }
    public ICollection<OtherViewModel> Others { get; set; }
}

Моя проверка проверяет каждый объект в Others на разные правила, используя контракт, предоставленный IValidatableObject:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    foreach (var other in this.Others)
    {
        // validate or yield return new ValidationResult
    }
}

Из-за сложной структуры реального MainViewModel мне пришлось создать настраиваемое связующее устройство, которое перестраивает модель и присваивает данные POST соответствующим компонентам. Проблема, которую я получаю, заключается в том, что ничто не проверяется, что приводит к ошибкам проверки на уровне контекста, поскольку это нарушает определенные ограничения базы данных, и я не уверен, что я делаю неправильно. Я предположил, что ModelState.IsValid будет ссылаться на Validate на моей модели просмотра, но, похоже, это не так.

Мое связующее устройство выглядит следующим образом:

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    int modelId = (int)controllerContext.RouteData.Values["id"];

    // query the database and re-build the components of the view model

    // iterate the POST data and assign to the model where necessary

    // should I be calling something here to validate the model before it passed to the controller?

    return model;
}

Любая помощь оценивается!

Validator.TryValidateObject

Хорошо, кажется, я немного ближе. Теперь я могу запустить мой метод IValidatableObject, добавив следующее в мое настраиваемое связующее устройство:

var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationResults, true);

Кажется, что Validator.TryValidateObject вызывает метод проверки и установка последнего параметра true заставляет его проверять все свойства. Тем не менее, я теперь застрял в получении validationResults к контроллеру, чтобы их можно было использовать значимым образом.

Ответ 1

Я должен был понять, что я мог бы использовать ModelState.AddModelError через настраиваемое связующее, мне удалось заставить это работать правильно, добавив следующее в мое настраиваемое связующее устройство перед возвратом модели контроллеру:

var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationResults, true);
if (!isValid)
{
    foreach (var result in validationResults)
    {
        bindingContext.ModelState.AddModelError("", result.ErrorMessage);
    }
}

return model;

Теперь это возвращает список всех ошибок на моей странице, а проверка ModelState.IsValid моего действия с контроллером теперь возвращается false.

Ответ 2

Пол отличный ответ может быть реорганизован в общий метод validate-and-convert в ModelState следующим образом (например, в помощнике или базе CustomModelBinder). Кроме того, привязки к проверенным свойствам сохраняются.

public static void DoValidation(ModelBindingContext bindingContext, 
                                IValidatableObject model)
{
    var validationResults = new HashSet<ValidationResult>();
    var isValid = Validator.TryValidateObject(model, 
        new ValidationContext(model, null, null), validationResults, true);
    if (!isValid)
    {
        var resultsGroupedByMembers = validationResults
            .SelectMany(_ => _.MemberNames.Select(
                 x => new {MemberName = x ?? "", 
                           Error = _.ErrorMessage}))
            .GroupBy(_ => _.MemberName);

        foreach (var member in resultsGroupedByMembers)
        {
            bindingContext.ModelState.AddModelError(
                member.Key,
                string.Join(". ", member.Select(_ => _.Error)));
        }
    }
}