Lets start with what we know:
As the description suggests, if we have our models:
Model A:
public class A
{
public B ModelB { get; set; }
}
Model B:
public class B : IValidatableObject
{
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> errors = new List<ValidationResult>();
if (string.IsNullOrEmpty(Name)) {
errors.Add(new ValidationResult("Please enter your name"));
}
return errors;
}
}
And our view:
@model A
@Html.ValidationSummary(true)
@using (Html.BeginForm())
{
@Html.EditorFor(model => model.ModelB.Name)
<input type="submit" value="submit" />
}
Then the editor will output the line:
<input class="text-box single-line" id="ModelB_Name" name="ModelB.Name" type="text" value="" />
If we have our post action defined as:
[HttpPost]
public ActionResult Index(A model)
{
if (ModelState.IsValid)
{
return RedirectToAction("NextAction");
}
return View();
}
Then when binding to the A
model, the DefaultModelBinder
will look for the property named ModelB.Name
which it will find and bind to successfully.
However, the model validation performed by the DefaultModelBinder
against the A
model, will call the validation defined for Model B
. This validation will return an error message which is not defined against a property but because this validation is part of a complex model it is added to the ModelState with the key of "ModelB".
When the ValidationSummary
is called it looks for keys which are blank (i.e. defined for the model and not the property). As no blank keys exist, no error is displayed.
As a simple workaround, you could define an EditorTemplate
for the B
model.
In the folder containing your view, define a folder named EditorTemplates
and in this create a view with the same name as the model (e.g. B.cshtml
in my case). The contents of the Editor Template would be defined as:
@model MvcApplication14.Models.B
@Html.EditorFor(m => m.Name)
Then, modify the main view so that the EditorFor
looks like:
@Html.EditorFor(model => model.ModelB, null, "")
The "" specifies that any fields output by our editor template will not have a name prefixed to the field. Therefore, the output will now be:
<input class="text-box single-line" id="Name" name="Name" type="text" value="" />
However, this will now prevent the binding of ModelB
so this would need to be bound seperately in the post action and added to our A
model:
[HttpPost]
public ActionResult Index(A modelA, B modelB)
{
modelA.ModelB = modelB;
if (ModelState.IsValid)
{
return RedirectToAction("NextAction");
}
return View();
}
Now, when the modelB
is bound, the validation message will be written to the ModelState
with the key "". Therefore, this can now be displayed with the @ValidationMessage()
routine.
Warning: The above workaround assumes that modelB
does not have the same field names as modelA
. If both modelB
and modelA
had a Name
field for example then the DefaultModelBinder
may not bind the fields to their correct equivelents. For example, if the A
model also has a field named Name
then it would have to be written to the view as:
@Html.EditorFor(model => model.Name, null, "modelA.Name")
to ensure that it is bound correctly.
Hopefully, this should allow you to achieve the desired result using the methods already defined within the MVC3 framework.