ASP.NET MVC数组模型绑定

在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)并结合前端处理。

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

    public class OrderModel
    {
        /// <summary>
        /// 航班号
        /// </summary>
        public string FlightNo { get; set; }
        /// <summary>
        /// 乘机人
        /// </summary>
        public List<Passenger> Passengers { get; set; }
    }

    public class Passenger
    {
        public string Name { get; set; }
        public string IdNo { get; set; }
    }

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

        public ActionResult New()
        {
            Models.OrderModel orderModel = new Models.OrderModel();
            List<Models.Passenger> passenger = new List<Models.Passenger>();
            passenger.Add(new Models.Passenger());
            orderModel.Passengers = passenger;
            return View(orderModel);
        }

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

    <div style="width:680px">
        <div class="form-group">
            <label>航班</label><br/>
            @Html.TextBoxFor(p => p.FlightNo, new { placeholder = "航班号" })
        </div>
        <div class="form-group">
            <label>乘机人</label>
            <table class="passenger" >
                <tbody>
                    @if (Model.Passengers != null && Model.Passengers.Count > 0) {
                        for(int i = 0; i < Model.Passengers.Count; i++) {
                            <tr>
                                <td>姓名:</td>
                                <td>@Html.TextBoxFor(p => Model.Passengers[i].Name)</td>
                                <td>身份证号:</td>
                                <td>@Html.TextBoxFor(p => Model.Passengers[i].IdNo)</td>
                                <td>
                                    <a href="javascript:;" onclick="removePassenger(this)" >删除</a>
                                </td>
                            </tr>
                        }
                    }
                </tbody>
            </table>
            <div style="margin-top:10px">
                <a href="javascript:;" onclick="addPassenger()">添加乘机人</a>
            </div>
        </div>
    </div>

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

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

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

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

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

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

这样,当我们提交表单时,乘机人信息就会自动填充到模型的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进行绑定:

<script src="~/Scripts/vue.js"></script>
<script type="text/javascript">
        // 视图模型
        var viewModel = {
            FlightNo: ‘‘,
            Passengers: [
                { ElementId: ‘passenger_1‘, Name: ‘‘, IdNo: ‘‘ }
            ]
        }
        // 模型绑定
        new Vue({
            el: ‘#app‘,
            data: viewModel,
            methods: {
                removePassenger: function (elementId) {
                    for (var i = 0; i < viewModel.Passengers.length; i++) {
                        if (viewModel.Passengers[i].ElementId == elementId) {
                            viewModel.Passengers.splice(i, 1);
                        }
                    }
                },
                addPassenger: function () {
                    var tb = document.getElementsByTagName(‘table‘)[0];
                    var index = tb.rows[tb.rows.length - 1].getElementsByTagName(‘input‘)[0].getAttribute("id").split(‘_‘)[1];
                    viewModel.Passengers.push({ Name: ‘‘, IdNo: ‘‘, ElementId: ‘passenger_‘ + (index + 1) });
                },
                submitForm: function () {
                    var jsonString = JSON.stringify(viewModel);
                    document.getElementById("viewModel").value = jsonString;
                    return true;
                }
            }
        });
</script>

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

<form action="/Order2/NewPost" method="post">
    <div id="app" style="width:680px">
        <div class="form-group">
            <label>航班</label><br />
            <input v-model="FlightNo" type="text" placeholder="航班号" />
        </div>
        <div class="form-group">
            <label>乘机人</label>
            <table class="passenger">
                <tbody>
                    <tr v-for="passenger in Passengers">
                        <td>姓名:</td>
                        <td><input v-model="passenger.Name" v-bind:id="passenger.ElementId" type="text" /></td>
                        <td>身份证号:</td>
                        <td><input v-model="passenger.IdNo" type="text" /></td>
                        <td>
                            <a href="javascript:;" v-on:click="removePassenger(passenger.ElementId)">删除</a>
                        </td>
                    </tr>
                </tbody>
            </table>
            <div style="margin-top:10px">
                <a href="javascript:;" v-on:click="addPassenger">添加乘机人</a>
            </div>
            <div style="margin-top:10px">
                <input type="submit" class="btn btn-default" v-on:click="submitForm" />
            </div>
        </div>
    </div>
    <input type="hidden" id="viewModel" name="viewModel" />
</form>

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

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

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

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

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

时间: 2024-10-02 10:48:47

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

MVC数组模型绑定

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

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

一.什么是模型绑定? 模型绑定存在的意义就是为Action的参数提供值,例如:如下表单中提交了数据,那么Action(即:Index)的参数Id,Name的值就是表单中对应的name属性相同的值,而表单提交的值是如何赋值给Action的参数的呢?模型绑定就是来完成从用户提交的请求中提取数据,并赋值给Action的参数.此例是从表单中的提取数据,并赋值给Action的参数,模型绑定还可以完成完成从地址Url.路由Route.上传文件等中获取数据,并赋值给Action相应的参数. <form id=

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

用过MVC的兄弟们都知道,MVC有模型绑定表单提交的数据功能,那么我也想偷个懒也写个WebForm版的模型绑定.这里主要定义一个泛型方法,然后通过反射把表单上对应属性名字的值赋值到反射创建类的属性上. 有注意的地方: 1.定义的模型类的属性名要和表单name的名字相对应 2.定义的泛型方法是通过 var form = context.Request.Form;   表单 POST过来的数据 public class DataModel { /// <summary> /// 从表单提交的数据中

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

前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法.本文我们来讲解.NET Core中的模型绑定. 话题 在ASP.NET Core之前MVC和Web APi被分开,也就说其请求管道是独立的,而在ASP.NET Core中,WebAPi和MVC的请求管道被合并在一起,当我们建立控

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

前言 相信一直关注我的园友都知道,我写的博文都没有特别枯燥理论性的东西,主要是当每开启一门新的技术之旅时,刚开始就直接去看底层实现原理,第一会感觉索然无味,第二也不明白到底为何要这样做,所以只有当你用到了,你再去看理论性的文章时才会豁然开朗,这是我一直以来学习技术的方法.本文我们来讲解.NET Core中的模型绑定. 话题 在ASP.NET Core之前MVC和Web APi被分开,也就说其请求管道是独立的,而在ASP.NET Core中,WebAPi和MVC的请求管道被合并在一起,当我们建立控

ASP.NET MVC编程——模型

1 ViewModel 是一种专门提供给View使用的模型,使用ViewModel的理由是实体或领域模型所包含的属性比View使用的多或少,这种情况下实体或领域模型不适合View使用. 2模型绑定 默认模型绑定器 通过DefaultModelBinder解析客户端传来的数据,为控制器的操作参数列表赋值.   显示模型绑定 使用UpdateModel和TryUpdateModel显示绑定模型,不会检验未绑定字段. 使用UpdateModel方法绑定模型时,如果绑定失败就会抛异常,而TryUpdat

ASP.NET Core MVC/WebAPi 模型绑定

1 public class Person 2 { 3 public string Name { get; set; } 4 public string Address { get; set; } 5 public int Age { get; set; } 6 } 1 $("#btnJson").on("click", function () { 2 var datajson = { Name: "Jeffcky", Age: 24, Addr

ASP.Net MVC Model(模型+验证)

本系列目录:ASP.NET MVC4入门到精通系列目录汇总 模型就是处理业务,想要保存.创建.更新.删除的对象. 注解(通过特性实现) DisplayName Required StringLength(20,MinimumLength=2) DataType(System.ComponentModel.DataAnnotations.DataType.MultilineText) RegularExpression(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-

Asp.Net Mvc之模型注解

正文: 命名空间: using System.ComponentModel;     using System.ComponentModel.DataAnnotations; 在实体对象的属性上贴上相对应的特性标签(本质是通过反射得到特性标签的类实例,再进行相对应的业务逻辑处理判断),由于通过EntityFrameWork生成的xx.tt模板每次生成操作之后都会覆盖掉原有的修改,所以使用以下方法来对类对象贴上特性标签而且也保证不会被模板覆盖: 新建一个类(这里叫StudentView) 新建一个