Propogate Changes to Nested Entities in MVC 3
I have two POCO classes a User and an Address. Address is a complex object, and a User has one Address. I want to create a single User view that allows the create/edit of an Address on the same form via MVC Scaffolding + Entity Framework + Repository Pattern. The Create User View works correctly, but when I try to make changes to the Address in the User Edit View, the changes don't get propagated. How do I force the changes to propogated down to the Address object from the User View? Everything else is auto generated.
POCO Classes
public class TestDB : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public DbSet<Address> Addresses { get; set; }
public DbSet<User> Users { get; set; }
}
public class Address
{
public int ID { get; set; }
public string Street1 { get; set; }
public string Street2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
}
public class User
{
public int ID { get; set; }
public string UserName { get; set; }
/*[ForeignKey("Address")]
public int AddressID { get; set; }*/
public virtual Address Address { get; set; }
}
Edit User View
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>User</legend>
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.Partial("CreateOrEdit", Model) %>
<legend>Address</legend>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.Street1) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.Street1)%>
<%: Html.ValidationMessageFor(model => model.Address.Street1)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.Street2)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.Street2)%>
<%: Html.ValidationMessageFor(model => model.Address.Street2)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.City)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.City)%>
<%: Html.ValidationMessageFor(model => model.Address.City)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.State)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.State)%>
<%: Html.ValidationMessageFor(model => model.Address.State)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.PostalCode)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.PostalCode)%>
<%: Html.ValidationMessageFor(model => model.Address.PostalCode)%>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Address.Country)%>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Address.Country)%>
<%: Html.ValidationMessageFor(model => model.Address.Country)%>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
Editor Template for Address:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<AddressTest.Models.Address>" %>
<script src="<%: Url.Content("~/Scripts/jquery-1.5.1.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script>
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Address</legend>
<%: Html.HiddenFor(model => model.ID) %>
<div class="editor-label">
<%: Html.LabelFor(model => model.Street1) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Street1) %>
<%: Html.ValidationMessageFor(model => model.Street1) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Street2) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Street2) %>
<%: Html.ValidationMessageFor(model => model.Street2) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.City) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.City) %>
<%: Html.ValidationMessageFor(model => model.City) %>
</div>
<div c开发者_Python百科lass="editor-label">
<%: Html.LabelFor(model => model.State) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.State) %>
<%: Html.ValidationMessageFor(model => model.State) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.PostalCode) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.PostalCode) %>
<%: Html.ValidationMessageFor(model => model.PostalCode) %>
</div>
<div class="editor-label">
<%: Html.LabelFor(model => model.Country) %>
</div>
<div class="editor-field">
<%: Html.EditorFor(model => model.Country) %>
<%: Html.ValidationMessageFor(model => model.Country) %>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
<div>
<%: Html.ActionLink("Back to List", "Index") %>
</div>
Change to User Edit View:
<% using (Html.BeginForm()) { %>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>User</legend>
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.Partial("CreateOrEdit", Model) %>
<%: Html.Editor("Address", Model.Address) %>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% } %>
The solution was to modify the User Update Repository Method and mark the entity state modified for the Address. I also needed to add a hidden property for the Address.ID on the User Edit View.
User Insert/Update Repository Method
public void InsertOrUpdate(User user)
{
if (user.ID == default(int)) {
// New entity
context.Users.Add(user);
} else {
// Existing entity
context.Entry(user.Address).State = EntityState.Modified; // Update Address
context.Entry(user).State = EntityState.Modified;
}
}
Additional Hidden Property on User Edit View
<%: Html.HiddenFor(model => model.ID) %>
<%: Html.HiddenFor(model => model.Address.ID) %> <!-- Added this line! -->
Controller Edit Action
// POST: /User/Edit/5
[HttpPost]
public ActionResult Edit(User user)
{
if (ModelState.IsValid) {
userRepository.InsertOrUpdate(user);
userRepository.Save();
return RedirectToAction("Index");
} else {
ViewBag.PossibleAddresses = addressRepository.All;
return View();
}
}
The trick here maybe to use editortemplates
Basically make a partial view in Views/Shared/EditorTemplates (create the folder if it doesn't exist) called Address and strongly typed to your address class. This template will contain your editor for the address (cut out of the above, and change model => model.Address to model=>model . Then take all of the address editors out of your view and put in EditorFor(model.Address) instead. I think this will generate slightly different fieldnames that will work.
精彩评论