Html.DropDownListFor 复杂子对象的绑定
Html.DropDownListFor binding for complex child object
我有以下代码 (ASP.NET MVC/C#)...为简洁起见简化了一些代码。
public class UserViewModel
{
public int UserId { get; set; }
public string Name { get; set; }
public AddressViewModel Address { get; set; }
public IEnumerable<SelectListItem> CitySelectList { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public string StreetAddress { get; set; }
public int CityId { get; set; }
}
在剃须刀视图中,我有以下内容:
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList)
加载时在下拉列表中选择了正确的城市(即...原始值),这样工作正常,但是当我提交表单时,CityId 属性 没有更新为新的所选值....它仍然包含原始城市的 ID。
我向名为 "SelectedCityId" 的 UserViewModel 添加了一个新的 属性 并将其绑定到下拉列表,以测试问题所在的复杂嵌套对象:
@Html.DropDownListFor(x => x.SelectedCityId, Model.CitySelectList)
当我使用此方法提交表单时,SelectedCityId 确实包含正确的 ID,因此当对象是简单对象时我可以看到模型绑定正常工作,但当它是嵌套子对象时则不然。
只是为了排除故障,我检查了邮政编码字段,因为它也绑定到地址 class 的子项 属性,但在使用类似的 TextBoxFor 助手时它确实绑定正确:
@Html.TextBoxFor(x => x.Address.PostalCode)
所以,我的问题是,当表达式将控件绑定到嵌套子 属性 时,为什么模型绑定似乎无法与 DropDownListFor 助手一起使用?我确定我遗漏了一些微不足道的东西,但我没有看到它是什么,也没有在这里找到类似的答案。
编辑:
这是来自 cshtml 文件
的完整 html 表格
@using (Html.BeginForm("MyProfile", "Account", FormMethod.Post, new { id = "myProfileForm" }))
{
@Html.HiddenFor(x => x.UserId)
@Html.HiddenFor(x => x.AddressId)
@Html.HiddenFor(x => x.Address.CityId)
<article class="mysettings">
<h1>Personal details</h1>
<table>
<tr>
<th>E-mail:</th>
<td>@Model.Username
<!--edit fields-->
<div class="edit_field" id="field3">
<label for="new_email">New Email:</label>
@Html.TextBoxFor(x => x.Username, new { id = "new_email" })
<input type="submit" value="save" class="gradient-button" id="submit3"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td></td>
</tr>
<tr>
<th>First name:</th>
<td>@Model.FirstName
<!--edit fields-->
<div class="edit_field" id="field1">
<label for="new_name">New First Name:</label>
@Html.TextBoxFor(x => x.FirstName, new { id = "new_name" })
<input type="submit" value="save" class="gradient-button" id="submit1"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field1" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Last name:</th>
<td>@Model.LastName
<!--edit fields-->
<div class="edit_field" id="field2">
<label for="new_last_name">New Last Name:</label>
@Html.TextBoxFor(x => x.LastName, new { id = "new_last_name" })
<input type="submit" value="save" class="gradient-button" id="submit2"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field2" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Display name:</th>
<td>@Model.DisplayName
<!--edit fields-->
<div class="edit_field" id="field_display">
<label for="new_display_name">New Display Name:</label>
@Html.TextBoxFor(x => x.DisplayName, new { id = "new_display_name" })
<input type="submit" value="save" class="gradient-button" id="submit_display"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_display" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Password:</th>
<td><input type="password" value="@Model.Membership.Password" disabled="disabled" style="border: none; background-color: white; padding: 0;" />
<!--edit fields-->
<div class="edit_field" id="field4">
<label for="new_password">New Password:</label>
@Html.PasswordFor(x => x.Membership.Password, new { id = "new_password" })
<input type="submit" value="save" class="gradient-button" id="submit4"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td></td>
</tr>
<tr>
<th>Cell phone:</th>
<td>@Model.CellPhone
<!--edit fields-->
<div class="edit_field" id="field_cell">
<label for="new_cell">New Cell Phone:</label>
@Html.TextBoxFor(x => x.CellPhone, new { id = "new_cell" })
<input type="submit" value="save" class="gradient-button" id="submit_cell"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_cell" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Home phone:</th>
<td>@Model.HomePhone
<!--edit fields-->
<div class="edit_field" id="field_home">
<label for="new_home">New Home Phone:</label>
@Html.TextBoxFor(x => x.HomePhone, new { id = "new_home" })
<input type="submit" value="save" class="gradient-button" id="submit_home"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_home" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Street Address:</th>
<td>@(Model.Address != null ? Model.Address.Address1 : "No address given")
<!--edit fields-->
<div class="edit_field" id="field5">
<label for="new_address">New Address:</label>
@Html.TextBoxFor(x => x.Address.Address1, new { id = "new_address" })
<input type="submit" value="save" class="gradient-button" id="submit5"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field5" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Town / City:</th>
<td>@(Model.Address != null ? Model.Address.City.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field6">
<label for="new_city">New City:</label>
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, new { @class = "f-item" })
<input type="submit" value="save" class="gradient-button" id="submit6"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field6" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Postal Code:</th>
<td>@(Model.Address != null ? Model.Address.PostalCode : "No address given")
<!--edit fields-->
<div class="edit_field" id="field7">
<label for="new_zip">New Postal Code:</label>
@Html.TextBoxFor(x => x.Address.PostalCode, new { id = "new_zip" })
<input type="submit" value="save" class="gradient-button" id="submit7"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field7" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>First Admin Division (State):</th>
<td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field8">
<label for="new_state">New First Admin Division (State):</label>
<input type="text" id="new_state"/>
<input type="submit" value="save" class="gradient-button" id="submit8"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field8" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Country:</th>
<td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Country.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field9">
<label for="new_country">New Country:</label>
<input type="text" id="new_country"/>
<input type="submit" value="save" class="gradient-button" id="submit9"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field8" class="gradient-button edit">Edit</a></td>
</tr>
</table>
</article>
}
以及两个控制器动作:
public ActionResult MyProfile()
{
IAccountService accountSvc = GetService<IAccountService>();
UserProfileServiceRequest request = new UserProfileServiceRequest();
int userId = int.Parse(SessionMgr.GetInstance().GetSessionValue(SessionTypes.UserId).ToString());
if (userId == 0)
{
return RedirectToAction("Login", "Account");
}
else
{
request.UserId = userId;
UserProfileServiceResponse response = accountSvc.GetUserProfile(request);
UserProfileViewModel model = AutoMapper.Mapper.Map<UserProfileViewModel>(response.UserProfile);
ICommonService commonSvc = GetService<ICommonService>();
GetCitiesServiceRequest citiesRequest = new GetCitiesServiceRequest { FirstAdminDivisionId = model.Address.City.FirstAdminDivision.FirstAdminDivisionId };
KeyValuePairServiceResponse citiesResponse = commonSvc.GetCitiesForSelect(citiesRequest);
model.CitySelectList = citiesResponse.KeyValuePairs.ToSelectList();
List<RoleViewModel> roles = (List<RoleViewModel>)SessionMgr.GetInstance().GetSessionValue(SessionTypes.Roles);
RoleViewModel consumerRole = roles.FirstOrDefault(x => x.Meaning.Equals(DataEnumerations.GetRoleMeaning(DataEnumerations.Role.Consumer)));
if (consumerRole != null)
{
GetConsumerTripsServiceRequest consumerTripsRequest = new GetConsumerTripsServiceRequest() { UserId = userId };
GetConsumerTripsServiceResponse consumerTripsResponse = commonSvc.GetConsumerTrips(consumerTripsRequest);
model.ConsumerTrips = AutoMapper.Mapper.Map<List<TripViewModel>>(consumerTripsResponse.Trips);
GetUserReviewsServiceRequest userReviewsRequest = new GetUserReviewsServiceRequest() { UserId = userId };
GetUserReviewsServiceResponse userReviewsResponse = commonSvc.GetUserReviews(userReviewsRequest);
model.UserReviews = AutoMapper.Mapper.Map<List<UserReviewViewModel>>(userReviewsResponse.UserReviews);
}
return View(model);
}
}
[HttpPost]
public ActionResult MyProfile(UserProfileViewModel model)
{
UpdateUserProfileServiceRequest request = AutoMapper.Mapper.Map<UpdateUserProfileServiceRequest>(model);
IAccountService accountSvc = GetService<IAccountService>();
accountSvc.UpdateUserProfile(request);
return RedirectToAction("MyProfile", "Account");
}
整个 AddressViewModel class:
public class AddressViewModel
{
public Int32 AddressId { get; set; }
public String Address1 { get; set; }
public String Address2 { get; set; }
public Int32 CityId { get; set; }
public String PostalCode { get; set; }
public CityViewModel City { get; set; }
}
以及整个 UserProfileViewModel:
public class UserProfileViewModel : BaseViewModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName { get; set; }
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public int AddressId { get; set; }
public AddressViewModel Address { get; set; }
public MembershipViewModel Membership { get; set; }
public List<TripViewModel> ConsumerTrips { get; set; }
public List<UserReviewViewModel> UserReviews { get; set; }
public IEnumerable<SelectListItem> CitySelectList { get; set; }
public int SelectedCityId { get; set; }
}
除了下拉列表
,您的表单还包含 属性 的隐藏输入
@Html.HiddenFor(x => x.Address.CityId)
....
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, ..)
实际上,您为同一个 属性 传回了 2 个值。 DefaultModelBinder
读取第一个(从原始值的隐藏输入)并设置 Address.CityId
的值。相同 属性(来自下拉列表)的任何后续值都将被忽略。
删除隐藏的输入,您的模型将与所选值正确绑定。
我有以下代码 (ASP.NET MVC/C#)...为简洁起见简化了一些代码。
public class UserViewModel
{
public int UserId { get; set; }
public string Name { get; set; }
public AddressViewModel Address { get; set; }
public IEnumerable<SelectListItem> CitySelectList { get; set; }
}
public class AddressViewModel
{
public int AddressId { get; set; }
public string StreetAddress { get; set; }
public int CityId { get; set; }
}
在剃须刀视图中,我有以下内容:
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList)
加载时在下拉列表中选择了正确的城市(即...原始值),这样工作正常,但是当我提交表单时,CityId 属性 没有更新为新的所选值....它仍然包含原始城市的 ID。
我向名为 "SelectedCityId" 的 UserViewModel 添加了一个新的 属性 并将其绑定到下拉列表,以测试问题所在的复杂嵌套对象:
@Html.DropDownListFor(x => x.SelectedCityId, Model.CitySelectList)
当我使用此方法提交表单时,SelectedCityId 确实包含正确的 ID,因此当对象是简单对象时我可以看到模型绑定正常工作,但当它是嵌套子对象时则不然。
只是为了排除故障,我检查了邮政编码字段,因为它也绑定到地址 class 的子项 属性,但在使用类似的 TextBoxFor 助手时它确实绑定正确:
@Html.TextBoxFor(x => x.Address.PostalCode)
所以,我的问题是,当表达式将控件绑定到嵌套子 属性 时,为什么模型绑定似乎无法与 DropDownListFor 助手一起使用?我确定我遗漏了一些微不足道的东西,但我没有看到它是什么,也没有在这里找到类似的答案。
编辑: 这是来自 cshtml 文件
的完整 html 表格@using (Html.BeginForm("MyProfile", "Account", FormMethod.Post, new { id = "myProfileForm" }))
{
@Html.HiddenFor(x => x.UserId)
@Html.HiddenFor(x => x.AddressId)
@Html.HiddenFor(x => x.Address.CityId)
<article class="mysettings">
<h1>Personal details</h1>
<table>
<tr>
<th>E-mail:</th>
<td>@Model.Username
<!--edit fields-->
<div class="edit_field" id="field3">
<label for="new_email">New Email:</label>
@Html.TextBoxFor(x => x.Username, new { id = "new_email" })
<input type="submit" value="save" class="gradient-button" id="submit3"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td></td>
</tr>
<tr>
<th>First name:</th>
<td>@Model.FirstName
<!--edit fields-->
<div class="edit_field" id="field1">
<label for="new_name">New First Name:</label>
@Html.TextBoxFor(x => x.FirstName, new { id = "new_name" })
<input type="submit" value="save" class="gradient-button" id="submit1"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field1" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Last name:</th>
<td>@Model.LastName
<!--edit fields-->
<div class="edit_field" id="field2">
<label for="new_last_name">New Last Name:</label>
@Html.TextBoxFor(x => x.LastName, new { id = "new_last_name" })
<input type="submit" value="save" class="gradient-button" id="submit2"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field2" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Display name:</th>
<td>@Model.DisplayName
<!--edit fields-->
<div class="edit_field" id="field_display">
<label for="new_display_name">New Display Name:</label>
@Html.TextBoxFor(x => x.DisplayName, new { id = "new_display_name" })
<input type="submit" value="save" class="gradient-button" id="submit_display"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_display" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Password:</th>
<td><input type="password" value="@Model.Membership.Password" disabled="disabled" style="border: none; background-color: white; padding: 0;" />
<!--edit fields-->
<div class="edit_field" id="field4">
<label for="new_password">New Password:</label>
@Html.PasswordFor(x => x.Membership.Password, new { id = "new_password" })
<input type="submit" value="save" class="gradient-button" id="submit4"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td></td>
</tr>
<tr>
<th>Cell phone:</th>
<td>@Model.CellPhone
<!--edit fields-->
<div class="edit_field" id="field_cell">
<label for="new_cell">New Cell Phone:</label>
@Html.TextBoxFor(x => x.CellPhone, new { id = "new_cell" })
<input type="submit" value="save" class="gradient-button" id="submit_cell"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_cell" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Home phone:</th>
<td>@Model.HomePhone
<!--edit fields-->
<div class="edit_field" id="field_home">
<label for="new_home">New Home Phone:</label>
@Html.TextBoxFor(x => x.HomePhone, new { id = "new_home" })
<input type="submit" value="save" class="gradient-button" id="submit_home"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field_home" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Street Address:</th>
<td>@(Model.Address != null ? Model.Address.Address1 : "No address given")
<!--edit fields-->
<div class="edit_field" id="field5">
<label for="new_address">New Address:</label>
@Html.TextBoxFor(x => x.Address.Address1, new { id = "new_address" })
<input type="submit" value="save" class="gradient-button" id="submit5"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field5" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Town / City:</th>
<td>@(Model.Address != null ? Model.Address.City.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field6">
<label for="new_city">New City:</label>
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, new { @class = "f-item" })
<input type="submit" value="save" class="gradient-button" id="submit6"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field6" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Postal Code:</th>
<td>@(Model.Address != null ? Model.Address.PostalCode : "No address given")
<!--edit fields-->
<div class="edit_field" id="field7">
<label for="new_zip">New Postal Code:</label>
@Html.TextBoxFor(x => x.Address.PostalCode, new { id = "new_zip" })
<input type="submit" value="save" class="gradient-button" id="submit7"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field7" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>First Admin Division (State):</th>
<td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field8">
<label for="new_state">New First Admin Division (State):</label>
<input type="text" id="new_state"/>
<input type="submit" value="save" class="gradient-button" id="submit8"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field8" class="gradient-button edit">Edit</a></td>
</tr>
<tr>
<th>Country:</th>
<td>@(Model.Address != null ? Model.Address.City.FirstAdminDivision.Country.Name : "No address given")
<!--edit fields-->
<div class="edit_field" id="field9">
<label for="new_country">New Country:</label>
<input type="text" id="new_country"/>
<input type="submit" value="save" class="gradient-button" id="submit9"/>
<a href="#">Cancel</a>
</div>
<!--//edit fields-->
</td>
<td><a href="#field8" class="gradient-button edit">Edit</a></td>
</tr>
</table>
</article>
}
以及两个控制器动作:
public ActionResult MyProfile()
{
IAccountService accountSvc = GetService<IAccountService>();
UserProfileServiceRequest request = new UserProfileServiceRequest();
int userId = int.Parse(SessionMgr.GetInstance().GetSessionValue(SessionTypes.UserId).ToString());
if (userId == 0)
{
return RedirectToAction("Login", "Account");
}
else
{
request.UserId = userId;
UserProfileServiceResponse response = accountSvc.GetUserProfile(request);
UserProfileViewModel model = AutoMapper.Mapper.Map<UserProfileViewModel>(response.UserProfile);
ICommonService commonSvc = GetService<ICommonService>();
GetCitiesServiceRequest citiesRequest = new GetCitiesServiceRequest { FirstAdminDivisionId = model.Address.City.FirstAdminDivision.FirstAdminDivisionId };
KeyValuePairServiceResponse citiesResponse = commonSvc.GetCitiesForSelect(citiesRequest);
model.CitySelectList = citiesResponse.KeyValuePairs.ToSelectList();
List<RoleViewModel> roles = (List<RoleViewModel>)SessionMgr.GetInstance().GetSessionValue(SessionTypes.Roles);
RoleViewModel consumerRole = roles.FirstOrDefault(x => x.Meaning.Equals(DataEnumerations.GetRoleMeaning(DataEnumerations.Role.Consumer)));
if (consumerRole != null)
{
GetConsumerTripsServiceRequest consumerTripsRequest = new GetConsumerTripsServiceRequest() { UserId = userId };
GetConsumerTripsServiceResponse consumerTripsResponse = commonSvc.GetConsumerTrips(consumerTripsRequest);
model.ConsumerTrips = AutoMapper.Mapper.Map<List<TripViewModel>>(consumerTripsResponse.Trips);
GetUserReviewsServiceRequest userReviewsRequest = new GetUserReviewsServiceRequest() { UserId = userId };
GetUserReviewsServiceResponse userReviewsResponse = commonSvc.GetUserReviews(userReviewsRequest);
model.UserReviews = AutoMapper.Mapper.Map<List<UserReviewViewModel>>(userReviewsResponse.UserReviews);
}
return View(model);
}
}
[HttpPost]
public ActionResult MyProfile(UserProfileViewModel model)
{
UpdateUserProfileServiceRequest request = AutoMapper.Mapper.Map<UpdateUserProfileServiceRequest>(model);
IAccountService accountSvc = GetService<IAccountService>();
accountSvc.UpdateUserProfile(request);
return RedirectToAction("MyProfile", "Account");
}
整个 AddressViewModel class:
public class AddressViewModel
{
public Int32 AddressId { get; set; }
public String Address1 { get; set; }
public String Address2 { get; set; }
public Int32 CityId { get; set; }
public String PostalCode { get; set; }
public CityViewModel City { get; set; }
}
以及整个 UserProfileViewModel:
public class UserProfileViewModel : BaseViewModel
{
public int UserId { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string DisplayName { get; set; }
public string CellPhone { get; set; }
public string HomePhone { get; set; }
public int AddressId { get; set; }
public AddressViewModel Address { get; set; }
public MembershipViewModel Membership { get; set; }
public List<TripViewModel> ConsumerTrips { get; set; }
public List<UserReviewViewModel> UserReviews { get; set; }
public IEnumerable<SelectListItem> CitySelectList { get; set; }
public int SelectedCityId { get; set; }
}
除了下拉列表
,您的表单还包含 属性 的隐藏输入@Html.HiddenFor(x => x.Address.CityId)
....
@Html.DropDownListFor(x => x.Address.CityId, Model.CitySelectList, ..)
实际上,您为同一个 属性 传回了 2 个值。 DefaultModelBinder
读取第一个(从原始值的隐藏输入)并设置 Address.CityId
的值。相同 属性(来自下拉列表)的任何后续值都将被忽略。
删除隐藏的输入,您的模型将与所选值正确绑定。