Reputation: 754398
I'm stumped with a problem in ASP.NET MVC 3 - handling editing and updating of a master-detail relationship from a single MVC view.
I'm using Entity Framework 4.2 code-first to build a small app. It uses a Person
class (which has Name
, FirstName
and so on), and an Address
class (with Street
, Zipcode
, City
etc.).
These two classes have a m:n relationship to one another, modelled in EF code-first with:
public class Person
{
private List<Address> _addresses;
// bunch of scalar properties like ID, name, firstname
public virtual List<Address> Addresses
{
get { return _addresses; }
set { _addresses = value; }
}
}
public class Address
{
private List<Person> _people;
// bunch of scalar properties like AddressID, street, zip, city
public virtual List<Person> People
{
get { return _people; }
set { _people = value; }
}
}
I filled the database manually with some data and retrieval of the data using EF 4.2 works just fine.
I have an ASP.NET MVC PersonController
, in which I am loading a Person
including all its current addresses, and show it using a Razor view. I'd like to allow the user to change or update existing addresses, as well as delete or insert addresses. The controller gets the data for that person from a WCF service like this:
public ActionResult Edit(int id)
{
Person person = _service.GetPerson(id);
return View(person);
}
and the person's addresses are filled properly when retrieving.
The view for this works fine - it shows all the data from the Person
instance as expected.
@model DomainModel.Person
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
@Html.HiddenFor(model => model.PersonId)
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
.... and a few more of those scalar properties being edited....
<hr/>
<h4>Addresses</h4>
@foreach (var item in Model.Addresses)
{
<tr>
<td>
@Html.EditorFor(modelItem => item.Street)
</td>
<td>
@Html.EditorFor(modelItem => item.ZipCode)
</td>
<td>
@Html.EditorFor(modelItem => item.City)
</td>
</tr>
}
<hr/>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
However, when I'm trying to handle the POST upon saving the person being edited, I cannot seem to get access to the addresses that were displayed (and possibly edited) in the view:
[HttpPost]
public ActionResult Edit(Person person)
{
if (ModelState.IsValid)
{
_service.UpdatePerson(person);
return RedirectToAction("Index");
}
return View(person);
}
The person.Addresses
property is always null in this case. Hmmm.... what am I missing?? How can I edit a Person-to-Addresses relationship on an ASP.NET MVC view, and get back the Person
AND its associated Addresses
from my view, so I can pass them to EF to save them back to the database tables??
Upvotes: 1
Views: 1215
Reputation: 4612
I believe you'll get the desired effect by changing the foreach block to a for block as follows:
@for (var i = 0; i < Model.Addresses.Count(); i++)
{
<tr>
<td>
@Html.EditorFor(model => model.Addresses[i].Street)
</td>
<td>
@Html.EditorFor(model => model.Addresses[i].ZipCode)
</td>
<td>
@Html.EditorFor(model => model.Addresses[i].City)
</td>
</tr>
}
This of course require that the Addresses collection is accessible by index.
The reason for this is that in the foreach
block the resulting html will be something like:
<input type="text" name="Street" value="Test Street 1" />
The for
block will instead generate something like:
<input type="text" name="Addresses[0].Street" value="Test Street 1" />
The model binder then uses the form field name to match it against the properties of the model class.
Both examples are simplified to get the point across and aren't 100% correct.
Upvotes: 2
Reputation: 1038730
I suspect that the input fields for the address have wrong names.
I would suggest you using editor templates. So replace your @foreach
loop in the Razor view with a single call of the EditorFor
helper for the Addresses property:
<h4>Addresses</h4>
@Html.EditorFor(x => x.Addresses)
<hr/>
and now define an editor template for the address which will automatically be rendered for each element of the collection (~/Views/Shared/EditorTemplates/Address.cshtml
):
@model Address
<tr>
<td>
@Html.EditorFor(x => x.Street)
</td>
<td>
@Html.EditorFor(x => x.ZipCode)
</td>
<td>
@Html.EditorFor(x => x.City)
</td>
</tr>
Now when you submit, the Addresses
property on your Person model will be correctly bound.
Remark: the editor template could also be placed inside ~/Views/XXX/EditorTemplates/Address.cshtml
where XXX is the current controller. In this case it can be reused only within the views of the current controller instead of making it globally visible (by putting it in Shared).
For more in-depth overview of how the wire format of the default model binder you may take a look at the following article.
Upvotes: 2