在ASP.NET MVC中使用Razor語法可以在視圖中方便地展示數(shù)組,如果要進行數(shù)組模型綁定,會遇到索引斷裂問題,如下示例:
1
2
3
4
5
6
7
8
9
|
< 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" /> |
數(shù)組Name在索引3處斷裂,在模型綁定器解析完成后,會丟棄后面的4和5,只有0、1、2會被正確解析到對應模型中。
這種斷裂在進行動態(tài)數(shù)組綁定時會經(jīng)常發(fā)生。
下面,以一個案例來探討如何進行動態(tài)數(shù)組綁定。假設(shè)有以下應用場景:
要求能夠動態(tài)地添加和刪除乘機人,最終提交表單后乘機人信息要填充到視圖模型中的一個數(shù)組或集合屬性中,以方便我們進行后續(xù)業(yè)務處理。
方式一:使用占位符替換
第一種方式我稱之為”占位符替換“,使用的是ASP.NET MVC默認的模型綁定器(DefaultModelBinder)并結(jié)合前端處理。
首先,第一步,根據(jù)業(yè)務場景設(shè)計視圖模型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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 ; } } |
其次,將此視圖模型傳遞給視圖:
1
2
3
4
5
6
7
8
|
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); } |
再在視圖文件中進行展示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
< 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].屬性名"的能力,所以可以在模板文件中以占位符的形式來表示數(shù)組下標:
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- 乘機人模板 --> <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> |
以上代碼中的"{}"是數(shù)組下標占位符。當添加乘機人時,可預先計算已有乘機人個數(shù),然后再使用JavaScript替換”{}“為數(shù)組下標。
1
2
3
4
5
6
7
8
|
// 添加乘機人 function addPassenger() { // 當前添加行數(shù)組元素下標 var index = $( ".passenger" ).find( "tbody" ).find( "tr" ).length; //{}是數(shù)組元素下標占位符 var passengerHTML = $( '#passengerTemplate' ).html().replace(/{}/g, index); $( ".passenger" ).find( "tbody" ).append(passengerHTML); } |
當刪除乘機人時,注意如果刪除的不是最后一個,會發(fā)生索引斷裂問題,需要重新調(diào)整數(shù)組下標:
1
2
3
4
5
6
7
8
9
10
11
|
// 刪除乘機人 function removePassenger(e) { $(e).parents( "tr" ).remove(); // 依次遍歷表格的每行,重新調(diào)整數(shù)組下標 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 + ']' ); //重新調(diào)整數(shù)組元素下標 tb.find( "tr" ).eq(i).html(newTR); } } |
這樣,當我們提交表單時,乘機人信息就會自動填充到模型的Passengers屬性中。
方式二:使用Vue.js
使用第一種方式需要編寫大量前端代碼,包括模板文件,添加刪除事件,還需要處理重新調(diào)整順序時的插值問題。
如果使用前端MVVM框架會讓這一流程變得簡單,目前比較流行的前端MVVM框架有AngularJS,有老古董KnockoutJS,也有新興小眾框架Vue.js。
AngularJS比較龐大,這么簡單的一個模型綁定用Anuglar有一種殺雞用牛刀的感覺;Knockout和Vue都是輕量級的MVVM框架,但Knockout需要包裹原生數(shù)據(jù)來制造可觀察對象,取值和賦值時需要采用函數(shù)調(diào)用的形式,使用起來不是很方便,所以我選擇了Vue.js。Vue.js是一個輕量高效的庫,它沒有像Angular的module、controller、scope、factory、service這種API,核心就是一個模型綁定功能。大小只有70kb,gzip壓縮后只有25kb,非常輕量化。
這種方式的基本原理是前端使用Vue.js聲明視圖模型并進行綁定,然后提交表單時把模型序列化為json字符串傳遞到后臺,后臺再使用Json.net反序列化為C#對象。
由于Vue.js的綁定特點,我們只需要操作數(shù)組元素即可,不需要額外關(guān)注DOM操作,節(jié)省了不少工作量。
首先,需要聲明視圖模型,并使用Vue.js進行綁定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
<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綁定:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
< 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#強類型模型:
1
2
3
4
5
6
7
8
9
10
11
|
[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" ); } |
這兩種方式均可以實現(xiàn)動態(tài)數(shù)組綁定,方式一使用js進行占位符替換,表單中的元素都以[index].屬性名的方式命名,然后由MVC默認的模型綁定器來轉(zhuǎn)化模型;
方式二使用Vue.js來直接進行模型綁定,提交表單時將模型序列化為json字符串,然后后端再反序列化,最終得到強類型模型。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助。