ASP.NET MVC 3 collecting/passing data from views w

2019-09-16 15:12发布

问题:

My model is a simple parent/child representation looking like this

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Child> Children { get; set; } 
}

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

Now, I want to create a page, which will allow me to edit/create children - one child at a time is just fine. I want to pass the parent into a view, create text box for Child.Name, collect whatever users types in and then save it into database.

So, in the first CreateParent action in my controller I create Parent (with Id passed in as I need to keep track of that Id [after all I need to know for which ParentId I need to add a child]) looks like this:

[HttpGet]
public ActionResult CreateChild(int id)
{
    var newParent = new Parent { 
        Id = id, Children = new List<Child> { new Child { Name = "ChildName" } } };
    return View("~/Views/Home/CreateParent.cshtml", newParent);
}

Page is loaded and user fills in the details and presses "OK". Then this action is run:

[HttpPost]
public ActionResult CreateChild(Parent parent)
{
      return View("~/Views/Home/ViewChildren.cshtml", AddChild(parent));
}

AddChild(parent) is a private function, which deals with persisting children for a given parent and it returns a parent model with updated children.

Here is my problem/question: data is lost between calls. When I create a parent in [HttpGet] everything is fine, I can see the values properly set in a debugger but when I fill in Child data and press "OK" in [HttpPost] Parent comes in with all values, including my newly populated Child as null (actually only Id is field seems to be carried over, which I also don't understand). Why?

I may add that if I do this with a view that's typed to a Child then this works. I can't really just pass a Child because I also need to track/carry-over Parent's Id. I don't understand this either.

Here are the views (first is to view children under a parent) and this one is working fine:

@model Parent

@{
    ViewBag.Title = "AddChild";
}
<h2>Children for parent @Model.Name </h2>
<p>
    @Html.ActionLink("Create New", "CreateChild", new {id = @Model.Id})
</p>

<table>
    <tr>
        <th>
            Name
        </th>
    <tr>
@foreach (var child in @Model.Children) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => child.Name)
        </td>
        <td>
            @Html.ActionLink("Delete", "Delete", new { id=child.Id })
        </td>
    </tr>
}
</table>

Here is view for creating a child under a parent:

@model Parent
@{
    ViewBag.Title = "CreateChild";
}

<h2>Create new child for @Model.Name</h2>

@using (Html.BeginForm())
{ 
    <table>
    <tr>
        <td>
            Name:
        </td>
    </tr>

@foreach (var child in @Model.Children)
{
    <tr>
        <td>
            @Html.EditorFor(modelItem => child.Name)
        </td>
    </tr>
}
</table>
<input id="submit" name="submit" type="submit" value="Save" />
}

So when I press "Save" Parent comes in only with Id set to the value that's been passed in. Everything else is set to null. Including Name of the Parent as well as Children.

Here is AddChild method:

private Parent AddChild(Parent parent)
{
   var updatedParent = GetParent(int parent.Id);
   updatedParent.Children.Add(parent.Children[0]); // this obviously fails as Children is NULL
   return updatedParent;
}

回答1:

Parent.Name is NULL because you are not POSTing it. You'd need to add a @Html.TextBoxFor() or @Html.HiddenFor(). But you don't need it cause you're loading the persisted parent.

For the children, try a for loop, instead.

@for (var n = 0; n < Model.Children.Count; n++)
{
    <tr>
        <td>
            @Html.EditorFor(modelItem => Model.Children[n].Name)
        </td>
    </tr>
}

This has to do with how ModelBinder works. Know it well, ModelBinder is your friend ;).

Edit: before I got all cute at the end, I meant to mention - pay attention to how the helper renders the inputs name attribute with the for loop (as opposed to the foreach loop) to understand what ModelBinder is looking for.