Asp:net MVC 3: @Html.EditorFor a subcollection of my model in a template?

23,084

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):

enter image description here

  • 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);
Share:
23,084
J4N
Author by

J4N

Updated on July 22, 2022

Comments

  • J4N
    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 of Html.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
    J4N over 12 years
    Amazing ! 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
    J4N over 12 years
    Because 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
    Denis Ivin over 12 years
    You want to specify different templates? If so, there is an overload for the EditorFor which accepts template name.
  • J4N
    J4N over 12 years
    Yes 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
    Denis Ivin over 12 years
    Yes, 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