在ASP.NET MVC中使用Razor语法可以在视图中方便地展示数组,如果要进行数组模型绑定,会遇到索引断裂问题,如下示例:

<input type="text" name="[0].Name" />

<input type="text" name="[1].Name" />

<input type="text" name="[2].Name" />

<input type="text" name="[4].Name" />

<input type="text" name="[5].Name" />

数组Name在索引3处断裂,在模型绑定器解析完成后,会丢弃后面的4和5,只有0、1、2会被正确解析到对应模型中。

这种断裂在进行动态数组绑定时会经常发生。

下面,以一个案例来探讨如何进行动态数组绑定。假设有以下应用场景:

要求能够动态地添加和删除乘机人,最终提交表单后乘机人信息要填充到视图模型中的一个数组或集合属性中,以方便我们进行后续业务处理。

方式一:使用占位符替换

第一种方式我称之为”占位符替换“,使用的是ASP.NET MVC默认的模型绑定器(DefaultModelBinder)并结合前端处理。

首先,第一步,根据业务场景设计视图模型:

  1. public class OrderModel
  2. {
  3. /// <summary>
  4. /// 航班号
  5. /// </summary>
  6. public string FlightNo { get; set; }
  7. /// <summary>
  8. /// 乘机人
  9. /// </summary>
  10. public List<Passenger> Passengers { get; set; }
  11. }
  12.  
  13. public class Passenger
  14. {
  15. public string Name { get; set; }
  16. public string IdNo { get; set; }
  17. }

其次,将此视图模型传递给视图:

  1. public ActionResult New()
  2. {
  3. Models.OrderModel orderModel = new Models.OrderModel();
  4. List<Models.Passenger> passenger = new List<Models.Passenger>();
  5. passenger.Add(new Models.Passenger());
  6. orderModel.Passengers = passenger;
  7. return View(orderModel);
  8. }

再在视图文件中进行展示:

  1. <div style="width:680px">
  2. <div class="form-group">
  3. <label>航班</label><br/>
  4. @Html.TextBoxFor(p => p.FlightNo, new { placeholder = "航班号" })
  5. </div>
  6. <div class="form-group">
  7. <label>乘机人</label>
  8. <table class="passenger" >
  9. <tbody>
  10. @if (Model.Passengers != null && Model.Passengers.Count > 0) {
  11. for(int i = 0; i < Model.Passengers.Count; i++) {
  12. <tr>
  13. <td>姓名:</td>
  14. <td>@Html.TextBoxFor(p => Model.Passengers[i].Name)</td>
  15. <td>身份证号:</td>
  16. <td>@Html.TextBoxFor(p => Model.Passengers[i].IdNo)</td>
  17. <td>
  18. <a href="javascript:;" onclick="removePassenger(this)" >删除</a>
  19. </td>
  20. </tr>
  21. }
  22. }
  23. </tbody>
  24. </table>
  25. <div style="margin-top:10px">
  26. <a href="javascript:;" onclick="addPassenger()">添加乘机人</a>
  27. </div>
  28. </div>
  29. </div>

由于ASP.NET MVC的模型绑定器(DefaultModelBinder)具备自动解析形如"[0].属性名"、"[1].属性名"的能力,所以可以在模板文件中以占位符的形式来表示数组下标:

  1. <!-- 乘机人模板 -->
  2. <script type="text/html" id="passengerTemplate">
  3. <tr>
  4. <td>姓名:</td>
  5. <td><input id="Passengers_{}__Name" name="Passengers[{}].Name" type="text" value=""></td>
  6. <td>身份证号:</td>
  7. <td><input id="Passengers_{}__IdNo" name="Passengers[{}].IdNo" type="text" value=""></td>
  8. <td>
  9. <a href="javascript:;" onclick="removePassenger(this)">删除</a>
  10. </td>
  11. </tr>
  12. </script>

以上代码中的"{}"是数组下标占位符。当添加乘机人时,可预先计算已有乘机人个数,然后再使用JavaScript替换”{}“为数组下标。

  1. // 添加乘机人
  2. function addPassenger() {
  3. // 当前添加行数组元素下标
  4. var index = $(".passenger").find("tbody").find("tr").length;
  5. //{}是数组元素下标占位符
  6. var passengerHTML = $('#passengerTemplate').html().replace(/{}/g, index);
  7. $(".passenger").find("tbody").append(passengerHTML);
  8. }

当删除乘机人时,注意如果删除的不是最后一个,会发生索引断裂问题,需要重新调整数组下标:

  1. // 删除乘机人
  2. function removePassenger(e) {
  3. $(e).parents("tr").remove();
  4. // 依次遍历表格的每行,重新调整数组下标
  5. var tb = $(".passenger").first();
  6. var count = tb.find("tbody").find("tr").length;
  7. for (var i = 0; i < count; i++) {
  8. var newTR = tb.find("tr").eq(i).formhtml().replace(/\[\d+\]/g, '[' + i + ']');//重新调整数组元素下标
  9. tb.find("tr").eq(i).html(newTR);
  10. }
  11. }

这样,当我们提交表单时,乘机人信息就会自动填充到模型的Passengers属性中。

方式二:使用Vue.js

使用第一种方式需要编写大量前端代码,包括模板文件,添加删除事件,还需要处理重新调整顺序时的插值问题。

如果使用前端MVVM框架会让这一流程变得简单,目前比较流行的前端MVVM框架有AngularJS,有老古董KnockoutJS,也有新兴小众框架Vue.js。

AngularJS比较庞大,这么简单的一个模型绑定用Anuglar有一种杀鸡用牛刀的感觉;Knockout和Vue都是轻量级的MVVM框架,但Knockout需要包裹原生数据来制造可观察对象,取值和赋值时需要采用函数调用的形式,使用起来不是很方便,所以我选择了Vue.js。Vue.js是一个轻量高效的库,它没有像Angular的module、controller、scope、factory、service这种API,核心就是一个模型绑定功能。大小只有70kb,gzip压缩后只有25kb,非常轻量化。

这种方式的基本原理是前端使用Vue.js声明视图模型并进行绑定,然后提交表单时把模型序列化为json字符串传递到后台,后台再使用Json.net反序列化为C#对象。

由于Vue.js的绑定特点,我们只需要操作数组元素即可,不需要额外关注DOM操作,节省了不少工作量。

首先,需要声明视图模型,并使用Vue.js进行绑定:

  1. <script src="~/Scripts/vue.js"></script>
  2. <script type="text/javascript">
  3. // 视图模型
  4. var viewModel = {
  5. FlightNo: '',
  6. Passengers: [
  7. { ElementId: 'passenger_1', Name: '', IdNo: '' }
  8. ]
  9. }
  10. // 模型绑定
  11. new Vue({
  12. el: '#app',
  13. data: viewModel,
  14. methods: {
  15. removePassenger: function (elementId) {
  16. for (var i = 0; i < viewModel.Passengers.length; i++) {
  17. if (viewModel.Passengers[i].ElementId == elementId) {
  18. viewModel.Passengers.splice(i, 1);
  19. }
  20. }
  21. },
  22. addPassenger: function () {
  23. var tb = document.getElementsByTagName('table')[0];
  24. var index = tb.rows[tb.rows.length - 1].getElementsByTagName('input')[0].getAttribute("id").split('_')[1];
  25. viewModel.Passengers.push({ Name: '', IdNo: '', ElementId: 'passenger_' + (index + 1) });
  26. },
  27. submitForm: function () {
  28. var jsonString = JSON.stringify(viewModel);
  29. document.getElementById("viewModel").value = jsonString;
  30. return true;
  31. }
  32. }
  33. });
  34. </script>

然后,在视图中使用Vue.js绑定:

  1. <form action="/Order2/NewPost" method="post">
  2. <div id="app" style="width:680px">
  3. <div class="form-group">
  4. <label>航班</label><br />
  5. <input v-model="FlightNo" type="text" placeholder="航班号" />
  6. </div>
  7. <div class="form-group">
  8. <label>乘机人</label>
  9. <table class="passenger">
  10. <tbody>
  11. <tr v-for="passenger in Passengers">
  12. <td>姓名:</td>
  13. <td><input v-model="passenger.Name" v-bind:id="passenger.ElementId" type="text" /></td>
  14. <td>身份证号:</td>
  15. <td><input v-model="passenger.IdNo" type="text" /></td>
  16. <td>
  17. <a href="javascript:;" v-on:click="removePassenger(passenger.ElementId)">删除</a>
  18. </td>
  19. </tr>
  20. </tbody>
  21. </table>
  22. <div style="margin-top:10px">
  23. <a href="javascript:;" v-on:click="addPassenger">添加乘机人</a>
  24. </div>
  25. <div style="margin-top:10px">
  26. <input type="submit" class="btn btn-default" v-on:click="submitForm" />
  27. </div>
  28. </div>
  29. </div>
  30. <input type="hidden" id="viewModel" name="viewModel" />
  31. </form>

在Controller里,我们反序列化即可得到对应的C#强类型模型:

  1. [HttpPost]
  2. public ActionResult NewPost()
  3. {
  4. var jsonString = Request.Form["viewModel"];
  5. Models.OrderModel model = Newtonsoft.Json.JsonConvert.DeserializeObject<Models.OrderModel>(jsonString);
  6. if (model != null) {
  7. // our code here...
  8. }
  9. return RedirectToAction("Index", "Home");
  10. }

这两种方式均可以实现动态数组绑定,方式一使用js进行占位符替换,表单中的元素都以[index].属性名的方式命名,然后由MVC默认的模型绑定器来转化模型;

方式二使用Vue.js来直接进行模型绑定,提交表单时将模型序列化为json字符串,然后后端再反序列化,最终得到强类型模型。

一个简单的示例程序(12e5)

ASP.NET MVC数组模型绑定的更多相关文章

  1. MVC数组模型绑定

    ASP.NET MVC数组模型绑定   在ASP.NET MVC中使用Razor语法可以在视图中方便地展示数组,如果要进行数组模型绑定,会遇到索引断裂问题,如下示例: <input type=& ...

  2. asp.net MVC 自定义模型绑定 从客户端中检测到有潜在危险的 Request.QueryString 值

    asp.net mvc 自定义模型绑定 有潜在的Requset.Form 自定义了一个模型绑定器.前端会传过来一些敏感字符.调用bindContext. valueProvider.GetValue( ...

  3. asp.net mvc 自定义模型绑定

    在asp.net mvc的控制器中如果能够活用模型的自动绑定功能的话能够减少许多工作量.但是如果我们想要对前台传来的数据进行一些处理再绑定到模型上,该怎么做呢? 这里用一个绑定用户数据的小案例来讲解a ...

  4. ASP.NET MVC 自定义模型绑定1 - 自动把以英文逗号分隔的 ID 字符串绑定成 List<int>

    直接贴代码了: CommaSeparatedModelBinder.cs using System; using System.Collections; using System.Collection ...

  5. 白话学习MVC(六)模型绑定

    一.什么是模型绑定? 模型绑定存在的意义就是为Action的参数提供值,例如:如下表单中提交了数据,那么Action(即:Index)的参数Id,Name的值就是表单中对应的name属性相同的值,而表 ...

  6. .net的WebForm模拟MVC进行模型绑定,让自己少操劳

    一.前言 用过MVC的兄弟们都知道,MVC有模型绑定表单提交的数据功能,那么我也想偷个懒也写个WebForm版的模型绑定.这里主要定义一个泛型方法,然后通过反射把表单上对应属性名字的值赋值到反射创建类 ...

  7. ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  8. ASP.NET Core MVC/WebAPi 模型绑定探索 转载https://www.cnblogs.com/CreateMyself/p/6246977.html

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

  9. 【转】ASP.NET Core MVC/WebAPi 模型绑定探索

    前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用 ...

随机推荐

  1. Reorder List

    题目: Given a singly linked list L: L0→L1→-→Ln-1→Ln,reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→- You must do ...

  2. Android 5.0/5.1开发问题专贴

    注:非5.0特定的开发问题,可以在这个帖子里查:Android开发问题汇总. 1.官方提供的例子android-support-v7-appcompat编译时提示android:actionModeS ...

  3. Python:常用函数封装

    def is_chinese(uchar): """判断一个unicode是否是汉字""" if uchar >= u'\u4e00' ...

  4. Python学习笔记(2):数据库访问

    本来打算继续用Access的,但费了非常大的劲,还是没有搞定.回过头,发现使用sqlite,简单到令人发指.干脆,把C#的CommonCode往这边迁移,先把AccessDB搬过来再说. 类结构和C# ...

  5. 如何实现在H5里调起高德地图APP?(上)

    这一篇文章,将讲述如何在H5里调起高德地图APP,并展示兴趣点.适合于展示某个餐馆,商场等,让用户自行选择前往方式. 场景一.在高德地图上展示Marker点或者POI标记 在一些基于位置分享的应用开发 ...

  6. AngularJS 中的 Promise 和 设计模式(转)

    原文地址:http://my.oschina.net/ilivebox/blog/293771 目录[-] Promise 简单例子 链式 Promise Parallel Promises And ...

  7. 【转】对 Xcode 菜单选项的详细探索(干货)

    http://www.cocoachina.com/ios/20151204/14480.html 本文调研Xcode的版本是 7.1,基本是探索了菜单的每一个按钮.虽然从xcode4一直用到了xco ...

  8. Hadoop学习篇 2 初识 Hadoop

    在一个全配置的集群上,运行Hadoop意味着在网络分布的不同服务器上运行一组守护进程 (daemons),这些守护进程或运行在单个服务器上,或运行与多个服务器上,他们包括: (1) NameNode( ...

  9. U盘安装ubuntu,一直提示start booting from usb device…[转]

    找到U盘中syslinux文件夹下的syslinux.cfg文件,在default vesamenu.c32前面加一个#号就可以了. 我的syslinux.cfg文件修改后如下,够简单吧!!!!建议用 ...

  10. Scala 深入浅出实战经典 第63讲:Scala中隐式类代码实战详解

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...