一般来说,对某个实体进行编辑都是没有问题的,比如微软官方的音乐商店,用自动生成的代码便可以顺利的搞定。但有一种业务情景是两个模型一同编辑,然后一同提交到Action,这时问题就来了。举一个例子,比如订单和订单明细。
一.列举所用到的实现类
/// <summary>
/// 订单
/// </summary>
public class Order
{
public int OrderID { set; get; }
[Display(Name = "订单简述")]
public string Description { set; get; }
[Display(Name = "创建人")]
public string CreatePerson { set; get; }
[Display(Name = "创建时间")]
public DateTime CreateDate { set; get; }
[Display(Name="订单类别")]
public int CategoryID { set; get; }
public List<OrderDetail> OrderDetailList { set; get; }
}
/// <summary>
/// 订单明细
/// </summary>
public class OrderDetail
{
public int OrderDetailID { set; get; }
public int OrderID { set; get; }
public string ProductName { set; get; }
public int Amount { set; get; }
public double UnitPrice { set; get; }
}
/// <summary>
/// 类别
/// </summary>
public class Category
{
public int CategoryID { set; get; }
public string CategoryName { set; get; }
}
红线加粗的实体,便是接下来演练主从实体同时编辑,再一并提交的主解。
二.定义两个Action,一个用以打开页面,一个用以响应客户端提交表单的请求。
- 基于Get请求的CreateOrder()用以展现页面,它通过View视图层来呈现定单主体Order,以及定单从体OrderDetail(基于Ajax异步加载来生成),在此之前,顺便初始化一下定单类型下拉框,它需要的是SelectList类:
[HttpGet]
public ActionResult CreateOrder()
{
var CategoryItems = new List<Category>() {
new Category(){
CategoryID = 10,
CategoryName = "公司内产"
},
new Category(){
CategoryID = 11,
CategoryName = "公司外购"
}
};
ViewData["Order.CategoryID"] = new SelectList(CategoryItems, "CategoryID", "CategoryName", "");
return View();
}
- 用以响应客户端提交表单的请求:
[HttpPost]
public ActionResult CreateOrder(Order order)
{
order.OrderDetailList = orderDetails;
return RedirectToAction("CreateOrder");
}
三.将从属Model做成PartialView
@model TelerikMvcApplication1.Models.OrderDetail
@using TelerikMvcApplication1.Helpers
<div class="editorRow">
@using (Html.BeginCollectionItem("Order.OrderDetailList"))
{
@Html.LabelFor(d => d.ProductName)
@Html.TextBoxFor(d => d.ProductName)
<br />
@Html.LabelFor(d => d.Amount)
@Html.TextBoxFor(d => d.Amount)
<br />
@Html.LabelFor(d => d.UnitPrice)
@Html.TextBoxFor(d => d.UnitPrice)
<hr />
<a href="#" class="deleteRow">delete</a>
}
</div>
需要注意的是 Html.BeginCollectionItem("Order.OrderDetailList")的参数命名极为重要,因为它是OrderDetail呈现于主页面时其名称(name)的前缀,同时该名称还包括一对中括号”[]“,且其中值为Guid,比如产品名称ProductName,在客户端的名称很可能是:
<input id="Order_OrderDetailList_5c9f0287-82a2-41f3-96b4-9c8b2459527c__ProductName"
type="text"
value=""
name="Order.OrderDetailList[5c9f0287-82a2-41f3-96b4-9c8b2459527c].ProductName">
四.主页面的设置
因为有从属模型OrderDetail的介入,所以主模型的名称也要统统加以前缀,否则本来正常的主模型也无法把提交的数据对应到主实体类上,原因暂不明:
<label for="@string.Format("Order.{0}", "Description")">订单简述</label>
@Html.TextBox(string.Format("Order.{0}", "Description"))
顺便列举出下拉框的绑定方法:
//Controller层
var CategoryItems = new List<Category>() {
new Category(){
CategoryID = 10,
CategoryName = "公司内产"
},
new Category(){
CategoryID = 11,
CategoryName = "公司外购"
}
};
ViewData["Order.CategoryID"] = new SelectList(CategoryItems, "CategoryID", "CategoryName", "");
//View层
@Html.DropDownList(string.Format("Order.{0}", "CategoryID"), @ViewData["Order.CategoryID"] as List<SelectListItem>, "--请选择--", new { })
五.订单明细的呈现
规划出订单明细呈现的区域,其实就是一个Div,它位于订单的下方,在实际项目中可以认真研究一下排版:
<fieldset>
<legend>订单明细</legend>
<div id="OrderDetailDiv">
</div>
</fieldset>
六.通过单击“+”号按钮动态加载一行新的明细,并且支持删除,当然这一切都是无刷新:
@using (Ajax.BeginForm("CreateOrderDetail", "Home", new AjaxOptions()
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "OrderDetailDiv",
OnSuccess = "SuccessCallBack"
}))
{
<input type="submit" id="btnAddDetail" value=" + " />
}
它以Post形式向服务端发起请求,当请求成功后,得到的返回值(HTML部分块)以“InsertAfter”方式进行添加,且更新于UpdateTargetId指定的区域,最后执行OnSuccess指定的JS函数。
七.主模型字段需要注意,各字段name需要加前缀:
@using (Html.BeginForm("CreateOrder", "Home", FormMethod.Post))
{
<input type="submit" value="全面提交" />
<hr />
<label for="@string.Format("Order.{0}", "Description")">订单简述
</label>
@Html.TextBox(string.Format("Order.{0}", "Description"))
<br />
<br />
<label for="@string.Format("Order.{0}", "CreatePerson")">创建人员
</label>
@Html.TextBox(string.Format("Order.{0}", "CreatePerson"))
<br />
<br />
<label for="@string.Format("Order.{0}", "CreateDate")">创建时间
</label>
@Html.TextBox(string.Format("Order.{0}", "CreateDate"))
<br />
<br />
<label for="@string.Format("Order.{0}", "CategoryID")">
</label>
@Html.DropDownList(string.Format("Order.{0}", "CategoryID"), @ViewData["Order.CategoryID"] as List<SelectListItem>, "--请选择--", new { })
<br />
<br />
<fieldset>
<legend>订单明细</legend>
<div id="OrderDetailDiv">
</div>
</fieldset>
}
建议调试方法:采用火狐浏览器的FireBug工具,和IE浏览器的HttpWatch