ASP.NET MVC 表单提交多层子级实体集合数据到控制器中
由于遇到了项目中实体类嵌套多层子级实体集合,并且子级实体集合的数据需要提交保存到数据库中的问题。针对此情况需要进行一些特殊的处理才可以将整个实体类及子级实体集合数据提交表单到控制器中,解决的方法是根据MVC视图中表单的命名规则来设置正确的子级实体集合所属的表单控件name属性,从而来获取提交的集合数据。
定义多层嵌套实体和假设场景
首先我们根据情况进行分析,假设当前有三个实体类,分别为公司类、部门类和员工类,公司类中包含该公司所属部门的实体集合,部门类又包含该部门所属员工的实体集合。
如下代码:
/// <summary> /// 公司实体类 /// </summary> public class Company { public int ID { get; set; } /// <summary> /// 公司名称 /// </summary> public string CompanyName { get; set; } /// <summary> /// 公司地址 /// </summary> public string Address { get; set; } /// <summary> /// 该公司所属的部门实体集合 /// </summary> public IList<Dept> Depts { get; set; } } /// <summary> /// 部门实体类 /// </summary> public class Dept { public int ID { get; set; } /// <summary> /// 所属公司ID /// </summary> public int CompanyId { get; set; } /// <summary> /// 部门名称 /// </summary> public string DeptName { get; set; } /// <summary> /// 该部门所属的员工实体集合 /// </summary> public IList<Employee> Employees { get; set; } } /// <summary> /// 员工实体类 /// </summary> public class Employee { public int ID { get; set; } /// <summary> /// 所属部门ID /// </summary> public int DeptId { get; set; } /// <summary> /// 员工姓名 /// </summary> public string EmployeeName { get; set; } }
那么现在我们假设要增加一个功能,能在页面上同时显示公司、部门和员工的信息,并且在页面上可以提交数据修改,那么我们就要使用到MVC的子级实体集合提交的技巧。
获取实体及子级实体集合在视图中对应的表单控件数据绑定方式
如果我们使用MVC的Helper类的Html.EditorFor方法来获取和绑定数据,那提交数据就显得十分简单了,首先在默认的Index控制器中动态添加了一些测试数据,代码如下:
[HttpGet] public ActionResult Index() { Company company = new Company(); company.Address="XXX省XXX市XXX区XXX街XXX号"; company.CompanyName="XXXXX公司"; company.ID=1; List<Dept> depts = new List<Dept>(); for (int i = 0; i < 5; i++) { Dept dept = new Dept(); dept.ID = i; dept.CompanyId = 1; dept.DeptName = string.Format("第[{0}]号部门",i.ToString()); List<Employee> employees = new List<Employee>(); for (int j = 0; j < 6; j++) { Employee employee = new Employee(); employee.ID = j; employee.EmployeeName = string.Format("员工{0}",j.ToString()); employee.DeptId = i; employees.Add(employee); } dept.Employees = employees; depts.Add(dept); } company.Depts = depts; return View(company); }
然后我们在视图页面的代码如下:
@model WebApplication5.Models.Company @using (Html.BeginForm("Edit","Home")) { <div> <h3>公司信息</h3> ID:@Html.EditorFor(c=>c.ID) 公司名称:@Html.EditorFor(c => c.CompanyName) 公司地址:@Html.EditorFor(c => c.Address) </div> <div> <h3>部门信息</h3> @for (int i = 0; i < Model.Depts.Count; i++) { <div> <label>ID:</label> @Html.EditorFor(c => c.Depts[i].ID) <label>部门名称:</label> @Html.EditorFor(c => c.Depts[i].DeptName) <label>所属公司ID:</label> @Html.EditorFor(c => c.Depts[i].CompanyId) </div> } </div> <div> <h3>员工信息</h3> @for (int i = 0; i < Model.Depts.Count; i++) { for (int j = 0; j < Model.Depts[i].Employees.Count; j++) { <div> <label>ID:</label> @Html.EditorFor(c => c.Depts[i].Employees[j].ID) <label>员工姓名:</label> @Html.EditorFor(c => c.Depts[i].Employees[j].EmployeeName) <label>部门ID:</label> @Html.EditorFor(c => c.Depts[i].Employees[j].DeptId) </div> } } </div> <div> <input type="submit" value="提交修改"/> </div> }
这里我们表单提交到Home控制器的Edit操作方法中。页面使用MVC的HtmlHelper类的EditorFor方法来绑定数据,HtmlHelper类是一个十分便利的类,根据EditorFor方法我们可以很简单就创建了数据和文本控件的绑定。执行程序最终页面显示的结果如下(页面简陋了点,不过作为测试页面,不用太在意细节):
我们可以看到无论是Company这个顶级实体,还是Company类下面的Dept子级实体集合,以及Dept类下的Employee子级实体集合,都成功的显示在界面上。
获取提交的子级实体集合数据的多种方法
接下来就是获取提交上来的数据,由于Html.EditorFor方法自动绑定了实体,所以在后台控制器中可以获取到Company类下所有的子级实体集合的数据。ASP.NET MVC 提供了多种获取子级实体的方法,最常用的是直接在控制器中声明顶级实体类 Company类作为参数,那么就可以通过Company实体来获取下属的Dept子级实体集合和Employee集合。如果想直接获取Dept子级实体集合,也可以直接在控制器中声明List<Dept> dept 作为参数。但是有一点要注意的是,控制器无法获取第三级以后的子级实体集合数据,因为这些子级数据必须依赖第二级的父实体。以下为表单提交到Home控制器中的Edit操作方法的代码:
/// <summary> /// 多种方法获取提交上来的子级实体集合数据 /// </summary> /// <param name="compay"></param> /// <param name="depts"></param> /// <param name="employees"></param> /// <returns></returns> public ActionResult Edit(Company compay,List<Dept> depts,List<Employee> employees) { /* * 通过Company顶级实体获取各级嵌套的子级实体集合数据 */ if (compay != null && compay.Depts != null) { //获取表单提交上来的公司所属部门实体集合 foreach (Dept dept in compay.Depts) { /* * 省略1000行相关操作代码....... */ //获取表单提交上来的公司所属下部门的所属员工自己实体集合数据 foreach (Employee employee in dept.Employees) { /* * 省略1000行相关操作代码....... */ } } } /*-----------------------------------*/ /* * 通过提交上来的子级实体集合获取数据 */ foreach (Dept dept in depts) { /* * 省略1000行相关操作代码....... */ //获取所属员工信息 foreach (Employee employee in dept.Employees) { /* * 省略1000行相关操作代码....... */ } } /* * 注意:这里employees 为null * 由于员工作为第三级(最底级)的嵌套,所以无法直接通过提交上来的子级集合获取到,只能依附在Dept下 * 由此可见通过实体集合只能获取第二级的嵌套 */ if (employees != null) { foreach (Employee employee in employees) { /* * 省略1000行相关操作代码....... */ } } return Content(""); }
使用自定义的HTML标签控件获取和提交表单子级实体集合数据
上面是使用HtmlHelper帮助类的方法来绑定数据和提交数据,但是我们也可以直接使用html标签控件来提交和获取表单的子级数据。在这之前要先了解表单提交数据是如何与ASP.NET MVC控制器中Action方法的参数绑定在一起,这两者的关联是根据<input/>标签的name属性来关联的。假设当前表单中有一输入控件为<input type="text" name="ID" value="1"/>,那么在所提交的控制中,设置控制器操作方法的参数名为ID,就可以获取name属性等于"ID" 的input标签控件的值。
使用实体绑定的方式也是一个道理,这里可以使用IE11的F12元素选择工具来查看上面用Html.EditorFor方法绑定后最终生成的html代码。
Company实体的ID输入框最终生成HTML代码(这里为了代码简洁,经过部分处理):
<input name="ID" id="ID" type="text" value="1" />
Company实体下所属第一个部门的ID输入框最终生成HTML代码:
<input name="Depts[0].ID" id="Depts_0__ID" type="text" value="0" />
Company实体下所属的第一个部门下,所属的第一个员工ID输入框的HTML代码:
<input name="Depts[0].Employees[0].ID" id="Depts_0__Employees_0__ID" type="text" value="0"/>
从这里应该不难看出MVC对于实体子级集合属性命名规则:
- 顶级实体属性对应input标签的name属性的名称是一致的,比如Company.ID对应的就是<input type="text" name="ID"/>
- 子级实体的属性对应input标签的name属性的名称,则是父实体的子级集合属性名称加上索引,加上"."和属性名称。比如Company.Depts[0].ID对应的name属性为Depts[0].ID,完整的html代码为<input name="Depts[0].ID" type="text" value="0" />
- 如果是嵌套多级的子级集合,安照上面第二个命名规则即可。比如第三级的Employee子级,Company.Depts[0].Employees对应的name属性为Depts[0].Employees[0].ID
下面为整个使用html input标签来提交和绑定表单数据的完整代码:
@model WebApplication5.Models.Company @using (Html.BeginForm("Edit","Home")) { <div> <h3>公司信息</h3> ID:<input type="text" name="ID" value="@Model.ID"/> 公司名称:<input type="text" name="CompanyName" value="@Model.CompanyName"/> 公司地址:<input type="text" name="Address" value="@Model.Address"/> </div> <div> <h3>部门信息</h3> @for (int i = 0; i < Model.Depts.Count; i++) { <div> <label>ID:</label> <input type="text" name="Depts[@i].ID" value="@Model.Depts[i].ID" /> <label>部门名称:</label> <input type="text" name="Depts[@i].DeptName" value="@Model.Depts[i].DeptName" /> <label>所属公司ID:</label> <input type="text" name="Depts[@i].CompanyId" value="@Model.Depts[i].CompanyId" /> </div> } </div> <div> <h3>员工信息</h3> @for (int i = 0; i < Model.Depts.Count; i++) { for (int j = 0; j < Model.Depts[i].Employees.Count; j++) { <div> <label>ID:</label> <input type="text" name="Depts[@i].Employees[@j].ID" value="@Model.Depts[i].Employees[j].ID" /> <label>员工姓名:</label> <input type="text" name="Depts[@i].Employees[@j].EmployeeName" value="@Model.Depts[i].Employees[j].EmployeeName" /> <label>部门ID:</label> <input type="text" name="Depts[@i].Employees[@j].DeptId" value="@Model.Depts[i].Employees[j].DeptId" /> </div> } } </div> <div> <input type="submit" value="提交修改"/> </div> }