Asp:net MVC 3: @Html.EditorFor a subcollection of my model in a template?
Solution 1
You can simplify your code by introducing the EditorTemplate. Here is how:
- The main view remains pretty much the same except we replaced RenderPartial with EditorFor:
TestForm.cshtml
@model WebTestApplication.Models.ContainerObject
@{
ViewBag.Title = "TestForm";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@using (Html.BeginForm("TestFormResult", "Home", FormMethod.Post)) {
@Html.EditorFor(m => m.Title)
@Html.EditorFor(m => m.ObjectList);
<input type="submit" value="Submit" />
}
- Then create a folder named EditorTemplates under Views/Home (assuming your controller is Home):
- and add the following template for the ContainedObject:
ContainedObject.cshtml
@model WebTestApplication.Models.ContainedObject
<p>
@Html.DisplayFor(m => m.Text)
@Html.CheckBoxFor(m => m.IsSelected)
@Html.HiddenFor(m => m.Id)
@Html.HiddenFor(m => m.Text)
</p>
The editor will automatically iterate through the list of objects rendering the view for each of them. Hope it helps.
Solution 2
I found this thread while looking for something else related. Denis has the correct answer, but I thought I would add some syntax in case anyone else comes across this:
If you have an editor template named "SomeTemplate.cshtml" you can use it for a list of Item as follows in your view:
@for (var i = 0; i < Model.ObjectList.Count(); i++)
{
@Html.EditorFor(m => m.ObjectList[i], "SomeTemplate")
}
Then in your editor template:
@model WebTestApplication.Models.ContainedObject
<br />
@Html.Label(Model.Text);
@Html.CheckBoxFor(m => m.IsSelected);
@Html.HiddenFor(m => m.Id);
@Html.HiddenFor(m => m.Text);
J4N
Updated on July 22, 2022Comments
-
J4N almost 2 years
I've been stuck a long time to edit a subcollection of my model, the collection of the model was coming null.
I finally found a solution, but I find it a little dirty:
First my tests datas:
Model object:
public class ContainerObject { public String Title { get; set; } public List<ContainedObject> ObjectList { get; set; } }
Sub collection object:
public class ContainedObject { public int Id { get; set; } public String Text { get; set; } public Boolean IsSelected { get; set; } }
Controller method which generate the object
public ActionResult TestForm() { return View(new ContainerObject() { Title = "This is a sample title", ObjectList = new List<ContainedObject>() { new ContainedObject(){Id=1, IsSelected = true, Text="ObjectOne"}, new ContainedObject(){Id=2, IsSelected = false, Text="ObjectTwo"}, new ContainedObject(){Id=3, IsSelected = true, Text="ObjectThree"}, new ContainedObject(){Id=4, IsSelected = false, Text="ObjectFour"}, } }); }
Controller which receive the edited object
[HttpPost] public ActionResult TestFormResult(ContainerObject filledObject) { return View(); }
The view
@model WebTestApplication.Models.ContainerObject @{ ViewBag.Title = "TestForm"; } @using (Html.BeginForm("TestFormResult","Home", FormMethod.Post)){ @Html.EditorFor(x => x.Title) Html.RenderPartial("ContainedObject", Model.ObjectList); <input type="submit" value="Submit"/> }
The partial view(ContainedObject.cshtml)
@model IEnumerable<WebTestApplication.Models.ContainedObject> @{ ViewBag.Title = "ContainedObject"; int i = 0; } @foreach (WebTestApplication.Models.ContainedObject currentObject in Model) { <br /> @Html.Label(currentObject.Text); @Html.CheckBox("ObjectList[" + i + "].IsSelected", currentObject.IsSelected); @Html.Hidden("ObjectList[" + i + "].Id", currentObject.Id); @Html.Hidden("ObjectList[" + i + "].Text", currentObject.Text); i++; }
This is actually working, but I've one problem:
- I've to generate names myself and specify the property of the container object
I tried to use
Html.EditorFor
instead ofHtml.RenderPartial
in the view, the problem is that it generate me the name "ObjectList.[0].Id"(with a additional . between the property name and the accessor).I also tried to use only @Html.EditorFor in the partial view, but it create vars with the name of the object.
If I don't use any template, it works:
@model WebTestApplication.Models.ContainerObject @{ ViewBag.Title = "TestForm"; } @using (Html.BeginForm("TestFormResult", "Home", FormMethod.Post)) { @Html.EditorFor(x => x.Title) for (int i = 0; i < Model.ObjectList.Count; i++) { <br /> @Html.Label(Model.ObjectList[i].Text); @Html.CheckBoxFor(m => Model.ObjectList[i].IsSelected); @Html.HiddenFor(m => Model.ObjectList[i].Id); @Html.HiddenFor(m => Model.ObjectList[i].Text); } <br /><input type="submit" value="Submit"/> }
But here it's a simple template, but in my real case, I will have much more data, and this will be re-used multiple time. So what is my best option?
-
J4N over 12 yearsAmazing ! I didn't know that the editorFor or DisplayFor iterate automatically if it's a list! Is there a way to make it work when specifying the template it has to use?
-
J4N over 12 yearsBecause in facts I've several times some information which can be displayed either with checkbox(multiple selection) or with radio buton(one item selection), in the same controller(e.g. choosen value and the default value)
-
Denis Ivin over 12 yearsYou want to specify different templates? If so, there is an overload for the EditorFor which accepts template name.
-
J4N over 12 yearsYes I tried, but when I specify a template which is for one item and receive a list of this item, it throw an exception telling me that it's not the hopped type.
-
Denis Ivin over 12 yearsYes, it's a bit trickier. In this case you will actually need 2 templates - one of them would accept a list of ContainedObject and then loop through it and output each item using another template which is similar to what I put in the answer but of course it would have different implementation based on your needs. Please see the following snippet I created for you: pastie.org/3335267