Reputation: 310
Using MVC 4 Forms, I have a model that always contains four children in a List<T>
property. The view displays the model correctly with each of the four child models rendered with a Razor partial view. The problem is that when I submit/post, the model deserializes with a null value for the child list.
Model:
public class MyModel
{
public int SomeValue { get; set; }
public List<ChildModel> Children { get; set; }
...
}
View:
@model MyProject.Models.MyModel
@using (Html.BeginForm())
{
@Html.LabelFor(model => model.SomeValue)
@Html.Partial("ChildPartial", Model.Children[0])
@Html.Partial("ChildPartial", Model.Children[1])
@Html.Partial("ChildPartial", Model.Children[2])
@Html.Partial("ChildPartial", Model.Children[3])
<input type="submit" value="Save" />
}
Controller:
public class MyController : Controller
{
public ActionResult Index()
{
MyModel model = new MyModel();
model.Children = new List<ChildModel>();
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
model.Children.Add(new ChildModel());
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
//model.Children is null here
//do stuff
...
return RedirectToAction("Index", "SomeOtherController");
}
}
The ChildPartial
views are each rendering correctly, and I am entering values into the controls, but they are not deserialized into the List<ChildModel>
. I can only get the root level properties of MyModel
to deserialize in the Post method.
I have tried adding UpdateModel(model);
to the beginning of the controller Post method but no luck there. Any ideas?
Edit
ChildModel.cs:
public class ChildModel
{
public String Name { get; set; }
public double Ratio { get; set; }
...
}
ChildPartial.cshtml:
@model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>@Model.Name</span>
</div>
<div>
@Html.LabelFor(m => m.Ratio)
@Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
@Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
Upvotes: 1
Views: 3219
Reputation: 1038720
I would first recommend you reading about the specific syntax that the default model binder expects and the naming convention when binding to collections: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Once you compare the names of your input fields with the ones explained in this blog post you will pretty quickly understand why your code doesn't work. You are simply not following the standard naming convention.
In order to fix this I would recommend you using editor templates. So in your main view put the following:
@model MyProject.Models.MyModel
@using (Html.BeginForm())
{
@Html.LabelFor(model => model.SomeValue)
@Html.EditorFor(model => model.Children)
<input type="submit" value="Save" />
}
Then move your ChildPartial.cshtml
to ~/Views/Shared/EditorTemplates/ChildModel.cshtml
. Notice that the name of the template and the location is extremely important. Make sure you have followed it. And put this inside:
@model MyProject.Models.ChildModel
<div>
<div>
<div>
<span>@Model.Name</span>
</div>
<div>
@Html.LabelFor(m => m.Ratio)
@Html.TextBoxFor(m => m.Ratio, new { autocomplete = "off" })
@Html.ValidationMessageFor(m => m.Ratio)
</div>
</div>
...
</div>
Alright, now run your project, inspect the generated HTML and more specifically the names of the input fields compare them with your initial version and compare them to the blog post I have initially linked to in my answer and you will understand everything about how model binding to collections works in ASP.NET MVC.
Remark: in your child template you don't have a corresponding input field for the Name
property of your ChildModel. So don't be surprised if it is null in your controller. You simply never send a value to it when the form is submitted. If you want this to happen you could include it as a hidden field in your editor template:
@Html.HiddenFor(m => m.Name)
Upvotes: 3