As you've rightly noted, this is because you're using a partial. This is happening because Html.Partial
has no idea that it's operating on a collection, so it doesn't generate the names for your form elements with your intention of binding to a collection.
However, the fix in your case appears to be fairly straightforward. Rather than using Html.Partial
, you can simply change your partial into an EditorTemplate
and call Html.EditorFor
on that template instead. Html.EditorFor
is smart enough to know when it's handling a collection, so it will invoke your template for each item in the collection, generating the correct names on your form.
So to do what you need, follow these steps:
- Create an
EditorTemplates
folder inside your view's current folder (e.g. if your view is HomeIndex.cshtml
, create the folder HomeEditorTemplates
). The name is important as it follows a convention for finding templates.
- Place your partial view in that folder. Alternatively, put it in the
SharedEditorTemplates
folder.
- Rename your partial view to
Datapoint.cshtml
(this is important as template names are based on the convention of the type's name).
Now the relevant view code becomes:
// Note: I removed @ from Model here.
@foreach (var g in Model)
{
<h2>@g.Name</h2>
@Html.EditorFor(m => g.DataPoints)
<hr />
}
This ensures the separation of your views, as you had originally intended.
Update per comments
Alright, so as I mentioned below, the problem now is that the model binder has no way of associating a DataPoint
with the correct Group
. The simple fix is to change the view code to this:
for (int i = 0; i < Model.Count; i++)
{
<h2>@Model[i].Name</h2>
@Html.EditorFor(m => m[i].DataPoints)
<hr />
}
That will correctly generate the names, and should solve the model binding problem.
OP's addendum
Following John's answer I also included the missing properties on the Group table as HiddenFor's which game me the model back on the post.
@for (int i = 0; i < Model.Count(); i++)
{
@Html.HiddenFor(t => Model[i].ID)
@Html.HiddenFor(t => Model[i].BusinessUnitID)
@Html.HiddenFor(t => Model[i].SortOrder)
@Html.HiddenFor(t => Model[i].Name)
<h2>@Model[i].Name</h2>
@Html.EditorFor(m => Model[i].Datapoints)
<hr />
}
Update 2 - Cleaner solution
My advice for using an EditorTemplate
for each DataPoint
also applies to each Group
. Rather than needing the for
loop, again sprinkling logic in the view, you can avoid that entirely by setting up an EditorTemplate
for Group
. Same steps apply as above in terms of where to put the template.
In this case, the template would be Group.cshtml
, and would look as follows:
@model Jmp.StaticMeasures.Models.Group
<h2>@Model.Name</h2>
@Html.EditorFor(m => m.DataPoints)
<hr />
As discussed above, this will invoke the template for each item in the collection, which will also generate the correct indices for each Group
. Your original view can now be simplified to:
@model List<Jmp.StaticMeasures.Models.Group>
@using (Html.BeginForm())
{
// Other markup
@Html.EditorForModel();
}