I have a view model that implements IValidatableObject
that contains a string and a collection of another view model, something like this:
public sealed class MainViewModel
{
public string Name { get; set; }
public ICollection<OtherViewModel> Others { get; set; }
}
My validation checks each object in Others
against different rules using the contract provided by IValidatableObject
:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var other in this.Others)
{
// validate or yield return new ValidationResult
}
}
Because of the complex structure of the real MainViewModel
I have had to create a custom model binder which re-builds the model and assigns POST data to the relevant components. The problem that I'm getting is that nothing is getting validated resulting in validation errors at the context level as it violates certain database constraints and I'm not sure what I'm doing wrong - I assumed that ModelState.IsValid
would invoke the Validate
method on my view model but it doesn't seem to go down that way.
My model binder looks like this:
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's passed to the controller?
return model;
}
Any help appreciated!
Validator.TryValidateObject
OK, seems I'm a little closer. I can now get my IValidatableObject
method to run by adding the following to my custom model binder:
var validationResults = new HashSet<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model, null, null), validationResults, true);
Seems that Validator.TryValidateObject
invokes the validation method and setting the last parameter to true
causes it to validate all properties. However, I'm now stuck with getting the validationResults
to the controller so they can be used in a meaningful way.
Paul's great answer can be refactored into a generic validate-and-convert to
ModelState
method as follows (e.g. in a helper orCustomModelBinder
base). In addition, the bindings to the validated properties are retained.I should have realised that I could use the
ModelState.AddModelError
through a custom binder, I've managed to get this working correctly now by adding the following to my custom model binder before returning the model to the controller:This now returns a list of all errors to my page and the
ModelState.IsValid
check on my controller action is now returningfalse
.