Model binding with complex type

2019-07-08 08:10发布

问题:

I have made a test controller and view to test complex binding, but I can't seem to make it work.

Here is my ViewModel:

public class TestViewModel
{
    public SubTest MainTest { get; set; }

    public List<SubTest> SubTestList { get; set; }
}

public class SubTest
{
    public string Name { get; set; }
    public int Id { get; set; }
}

Here is my View:

@model TestViewModel    

@{
    using (Html.BeginForm())
    {
        <h2>Main</h2>

        <p>
            @Html.DisplayTextFor(m => m.MainTest.Id)
            =>
            @Html.DisplayTextFor(m => m.MainTest.Name)
        </p>

        <h2>Subs</h2>

        foreach (var sub in Model.SubTestList)
        {
            <p>
                @Html.DisplayTextFor(m => sub.Id)
                =>
                @Html.DisplayTextFor(m => sub.Name)
            </p>
        }

        <button type="submit">Submit</button>
    }
}

And here is my controller:

public ActionResult Test()
{
    TestViewModel tvm = new TestViewModel();
    tvm.MainTest = new SubTest() { Id = 0, Name = "Main Test" };

    tvm.SubTestList = new List<SubTest>()
    {
        new SubTest() { Id = 1, Name = "Sub Test 1" } ,
        new SubTest() { Id = 2, Name = "Sub Test 2" } ,
        new SubTest() { Id = 3, Name = "Sub Test 3" } ,
        new SubTest() { Id = 4, Name = "Sub Test 4" } ,
    };

    return View(tvm);
}

[HttpPost]
public ActionResult Test(TestViewModel tvm)
{
    return View(tvm);
}

When I load the page, everything displays correctly, but if I set a breakpoint in the POST method, I see that the parameter values are both null.

What am I doing wrong ?

回答1:

Firstly DisplayTextFor() does not generate form controls (input, textarea, select) therefore there is nothing for the form to post back.

Secondly, if you did want to edit the values of your model (say using a textbox), then you would need to use a for loop (or custom EditorTemplate for typeof SubTest) not a foreach loop for your collection property, for example

for (int i = 0; i < Model.SubTestList.Count; i++)
{
  @Html.TextBoxFor(m => m.SubTestList[i].Id)
  @Html.TextBoxFor(m => m.SubTestList[i].Name)
}

Or using an EditorTemplate (the name of the template must match your model type

In /View/Shared/EditorTemplates/SubTest.cshtml

@model yourAssembly.SubTest
@Html.TextBoxFor(m => m.Id)
@Html.TextBoxFor(m => m.Name)

and in the main view

@model TestViewModel 
....
@Html.EditorFor(m => m.SubTestList)

The EditorFor() method accepts IEnumerable<T> and is smart enough to rendered the html from the template for each item in the collection.