通過(guò)實(shí)例模擬ASP.NET MVC的Model綁定機(jī)制:數(shù)組
[續(xù)《通過(guò)實(shí)例模擬ASP.NET MVC的Model綁定機(jī)制:簡(jiǎn)單類(lèi)型+復(fù)雜類(lèi)型]》]基于數(shù)組和集合類(lèi)型的Model綁定機(jī)制比較類(lèi)似,對(duì)于綁定參數(shù)類(lèi)型或者參數(shù)類(lèi)型的某個(gè)屬性為數(shù)組或者集合,如果ValueProvider根據(jù)對(duì)應(yīng)的Key能夠匹配多條數(shù)據(jù),那么這些數(shù)據(jù)最終將會(huì)轉(zhuǎn)換為綁定的數(shù)組/集合的元素。此外,針對(duì)數(shù)組/集合的Model綁定還支持基于索引的方式。[源代碼從這里下載][本文已經(jīng)同步到《How ASP.NET MVC Works?》中]
一、基于名稱(chēng)的數(shù)組綁定
對(duì)于針對(duì)NameValueConllectionProvider來(lái)說(shuō),通過(guò)GetValue方法得到的ValueProviderResult的RawValue總是一個(gè)字符串?dāng)?shù)組(不論是否具有多條數(shù)據(jù)于指定的Key相匹配,如果只有一條匹配的數(shù)據(jù),RawValue就是一個(gè)具有一個(gè)元素的字符串?dāng)?shù)組)。當(dāng)我們調(diào)用ValueProviderResult的ConvertTo方法將提供的值轉(zhuǎn)換成某種類(lèi)型時(shí),如果目標(biāo)類(lèi)型是數(shù)組或者集合,那么RawValue代表的字符串?dāng)?shù)組元素將會(huì)轉(zhuǎn)換成目標(biāo)對(duì)象的元素;如果目標(biāo)類(lèi)型不屬于集合,那么參與數(shù)據(jù)轉(zhuǎn)換的僅僅是RawValue數(shù)組的第1個(gè)元素。
如下面的代碼片斷所示,在默認(rèn)的HomeController的默認(rèn)Action方法Index中,我們創(chuàng)建了一個(gè)NameValueCollectionValueProvider對(duì)象,作為數(shù)據(jù)源的NameValueCollection中包含了三個(gè)同名(foo)數(shù)據(jù)條目。我們調(diào)用它的GetValue方法得到一個(gè)ValueProviderResult對(duì)象,然后我們將該對(duì)象的RawValue呈現(xiàn)出來(lái)。最后我們調(diào)用該ValueProviderResult對(duì)象的ConvertTo對(duì)象將提供的值轉(zhuǎn)換為int[]和int,并將轉(zhuǎn)換后的值呈現(xiàn)出來(lái)。
1: public class HomeController : Controller
2: {
3: public void Index()
4: {
5: NameValueCollection dataSource = new NameValueCollection();
6: dataSource.Add("foo", "123");
7: dataSource.Add("foo", "456");
8: dataSource.Add("foo", "789");
9: NameValueCollectionValueProvider valueProvider = new NameValueCollectionValueProvider(dataSource, CultureInfo.InvariantCulture);
10:
11: ValueProviderResult result = valueProvider.GetValue("foo");
12: Response.Write(string.Format("RawValue: {0}<br/>", result.RawValue));
13: Response.Write(string.Format("ConvertTo(typeof(int[])): {0}<br/>", result.ConvertTo(typeof(int[]))));
14: Response.Write(string.Format("ConvertTo(typeof(int)): {0}<br/>", result.ConvertTo(typeof(int))));
15: }
16: }
運(yùn)行這個(gè)程序之后,我們會(huì)在瀏覽器中得到如下的輸出結(jié)果,上面針對(duì)NameValueConllectionProvider的論述可以從輸出結(jié)果中得到印證。
1: RawValue: System.String[]
2: ConvertTo(typeof(int[])): System.Int32[]
3: ConvertTo(typeof(int)): 123
NameValueConllectionProvider(FormValueProvider和QueryStringValueProvider)的數(shù)據(jù)值提供機(jī)制決定了Model綁定的默認(rèn)行為。如果綁定的目標(biāo)對(duì)象是一個(gè)數(shù)組或者集合,匹配的同名數(shù)據(jù)項(xiàng)將會(huì)作為目標(biāo)對(duì)象的元素。實(shí)際上HttpFileCollectionValueProvider的數(shù)據(jù)值提供機(jī)制也類(lèi)似,如果綁定的目標(biāo)對(duì)象類(lèi)型是一個(gè)HttpPostedFileBase數(shù)組,那么匹配的同名文件輸入元素都將作為其數(shù)據(jù)源。
1: <input name="Foo" type="text" ... />
2: <input name="Foo" type="text" ... />
3: <input name="Foo" type="text" ... />
4: <input name="Bar" type="file" ... />
5: <input name="Bar" type="file" ... />
6: <input name="Bar" type="file" ... />
假設(shè)針對(duì)具有如下定義的Action方法ActionMethod提交的標(biāo)單具有如上的輸入元素,在三個(gè)文本框中輸入的字符串將綁定到foo參數(shù),而通過(guò)三個(gè)文件輸入元素上傳得文件將會(huì)綁定給bar參數(shù)。
1: Public void ActionMethod(string[] foo, HttpPostedFileBase[] bar)
現(xiàn)在我們對(duì)用于模擬默認(rèn)Model綁定的自定義DefaultModelBinder進(jìn)行進(jìn)一步完善,使之對(duì)基于名稱(chēng)的數(shù)組綁定提供支持。如下面的代碼片斷所示,我們?cè)贐indModel方法中添加了針對(duì)數(shù)組類(lèi)型的Model綁定代碼,而具體的實(shí)現(xiàn)定義在BindArrayModel方法中。
1: public class DefaultModelBinder
2: {
3: //其他成員
4: public object BindModel(Type parameterType, string prefix)
5: {
6: if (!this.ValueProvider.ContainsPrefix(prefix))
7: {
8: return null;
9: }
10:
11: ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType);
12: if (!modelMetadata.IsComplexType)
13: {
14: return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);
15: }
16: if (parameterType.IsArray)
17: {
18: return BindArrayModel(parameterType, prefix);
19: }
20: object model = CreateModel(parameterType);
21:
22: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))
23: {
24: string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;
25: property.SetValue(model, BindModel(property.PropertyType, key));
26: }
27: return model;
28: }
29: private object BindArrayModel(Type parameterType, string prefix)
30: {
31: IList list = new List<object>();
32: if (this.ValueProvider.ContainsPrefix(prefix))
33: {
34: IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(parameterType) as IEnumerable;
35: if (null != enumerable)
36: {
37: foreach (var value in enumerable)
38: {
39: list.Add(value);
40: }
41: }
42: }
43: Array array = Array.CreateInstance(parameterType.GetElementType(), list.Count);
44: list.CopyTo(array,0);
45: return array;
46: }
47: }
定義在BindArrayModel方法中針對(duì)數(shù)組的Model綁定邏輯很簡(jiǎn)單,我們直接通過(guò)ValueProvider將通過(guò)指定前綴得到的數(shù)據(jù)值轉(zhuǎn)換為IEnumerable類(lèi)型,并進(jìn)一步添加到一個(gè)List<object>對(duì)象中,最終我們將該List<object>對(duì)象的元素拷貝到一個(gè)創(chuàng)建的數(shù)組對(duì)象并將其作為Model對(duì)象返回。
為了演示針對(duì)數(shù)組的Model綁定,我們按照如下的方式修改了Action方法。該方法具有兩個(gè)參數(shù)foo和bar,前者是一個(gè)字符串?dāng)?shù)組,后者的類(lèi)型Bar的Baz屬性是一個(gè)整型數(shù)組。在Action方法中,我們將foo參數(shù)和bar參數(shù)的Baz屬性代表數(shù)組元素呈現(xiàn)出來(lái)。
1: public class HomeController : Controller
2: {
3: //其他成員
4: private IValueProvider GetValueProvider()
5: {
6: NameValueCollection requestData = new NameValueCollection();
7:
8: requestData.Add("foo", "abc");
9: requestData.Add("foo", "xyz");
10:
11: requestData.Add("bar.baz", "123");
12: requestData.Add("bar.baz", "456");
13:
14: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
15: }
16:
17: public void Action(string[] foo, Bar bar)
18: {
19: Response.Write("foo: <br/>");
20: Array.ForEach(foo,item=> Response.Write(" "+ item + "<br/>"));
21: Response.Write("bar.Baz: <br/>");
22: Array.ForEach(bar.Baz, item => Response.Write(" " + item + "<br/>"));
23: }
24: }
25:
26: public class Bar
27: {
28: public int[] Baz { get; set; }
29: }
通過(guò)GetValueProvider方法提供的NameValueCollectionValueProvider具有針對(duì)這兩個(gè)參數(shù)的數(shù)據(jù)源,從上面的代碼片斷所示,針對(duì)參數(shù)foo和bar的數(shù)據(jù)項(xiàng)具有相同的名稱(chēng)(foo和bar.baz)。該程序運(yùn)行之后會(huì)在瀏覽器中得到如下所示的輸出結(jié)果。
1: foo:
2: abc
3: xyz
4: bar.Baz:
5: 123
6: 456
二、基于索引的數(shù)組綁定
對(duì)于存在于作為ValueProvider數(shù)據(jù)源的NameValueCollection/Dictionary<string, object>中的數(shù)據(jù)項(xiàng)來(lái)說(shuō),如果它們綁定的對(duì)象是一個(gè)數(shù)組,可以采用相同的名稱(chēng)。這樣的Model綁定方式僅僅是用于元素為簡(jiǎn)單類(lèi)型的數(shù)組。除此之外,也可以采用格式為“[index]”的基于索引的前綴來(lái)表示。
ValueProvider基于索引的匹配策略也可以通過(guò)HtmlHelper<TModel>的模板方法EditorFor來(lái)體現(xiàn)。如下面的代碼片斷所示,在一個(gè)Model類(lèi)型為Contact數(shù)組的強(qiáng)類(lèi)型View中,我們調(diào)用HtmlHelper<TModel>的擴(kuò)展方法EditorFor將數(shù)組的前兩個(gè)元素的相關(guān)信息以編輯模式呈現(xiàn)出來(lái)。
1: @model Contact[]
2: @Html.EditorFor(m => m[0].Name)
3: @Html.EditorFor(m => m[0].PhoneNo)
4: @Html.EditorFor(m => m[0].EmailAddress)
5:
6: @Html.EditorFor(m => m[1].Name)
7: @Html.EditorFor(m => m[1].PhoneNo)
8: @Html.EditorFor(m => m[1].EmailAddress)
下面的XML片斷代表了上面這段代碼在最終生成的HTML中對(duì)應(yīng)的6個(gè)類(lèi)型為“text”的<input>元素,我們可以清楚地看到它們的名稱(chēng)被添加了[0]和[1]這樣的索引前綴。如果這些元素存在于一個(gè)提交的標(biāo)單中,并且目標(biāo)Action方法包含一個(gè)匹配的Contact數(shù)組類(lèi)型的參數(shù),Model綁定系統(tǒng)將最終生成兩個(gè)元素的Contact數(shù)組作為其參數(shù)值,數(shù)組中元素的順序與索引數(shù)值保持一致。
1: <input name="[0].Name" type="text" value="" .../>
2: <input name="[0].PhoneNo" type="text" value="" .../>
3: <input name="[0].EmailAddress" type="text" value="" .../>
4:
5: <input name="[1].Name" type="text" value="" .../>
6: <input name="[1].PhoneNo" type="text" value="" .../>
7: <input name="[1].EmailAddress" type="text" value="" .../>
基于數(shù)組的Model綁定采用“基零索引”,即將作為數(shù)組下邊界的索引前綴必須是“[0]”。此外,還要求索引在數(shù)值上必須是連續(xù)的。舉個(gè)簡(jiǎn)單的例子,假設(shè)提交的標(biāo)單中具有如下6個(gè)類(lèi)型為“hidden”的<input>元素,它們采用了基于索引的命名,并且從數(shù)字上看索引不是連續(xù)的(缺了一個(gè)[3])。
1: <input name="[0]" type="hidden" value="foo" />
2: <input name="[1]" type="hidden" value="bar" />
3: <input name="[2]" type="hidden" value="baz" />
4:
5: <input name="[4]" type="hidden" value="123" />
6: <input name="[5]" type="hidden" value="456" />
7: <input name="[6]" type="hidden" value="789" />
如果提供的標(biāo)單對(duì)應(yīng)如下所示的Action方法,上述的<input>元素值將會(huì)綁定到字符串?dāng)?shù)組類(lèi)型的參數(shù)array上。由于索引值不具有連續(xù)性,會(huì)導(dǎo)致后面的三個(gè)<input>元素值(“123”、“456”和“789”)會(huì)被丟棄,也就是說(shuō)綁定后的array參數(shù)值僅僅具有三個(gè)元素(“foo”、“bar”和“baz”)。
1: public ActionResult Index(string[] array);
除了采用基零整數(shù)作為數(shù)組索引之外,我們還可以采用任意字符串作為其索引,但是作為索引的字符串需要和數(shù)組元素值一樣存在于ValueProvider的數(shù)據(jù)源中。索引數(shù)據(jù)項(xiàng)名稱(chēng)為“index”,并且與數(shù)組元素?cái)?shù)據(jù)項(xiàng)具有相同的前綴。同樣以上面這個(gè)參數(shù)類(lèi)型為字符串?dāng)?shù)組的Action方法為例,我們可以通過(guò)提交具有如下內(nèi)容的表單來(lái)調(diào)用這個(gè)Action方法并為之提供相應(yīng)的參數(shù)值。
1: <input name="index" type="hidden" value="first" />
2: <input name="index" type="hidden" value="second" />
3: <input name="index" type="hidden" value="third" />
4:
5: <input name="[first]" type="text" value="foo" />
6: <input name="[second]" type="text" value="bar" />
7: <input name="[third]" type="text" value="baz" />
被提交標(biāo)單中三個(gè)類(lèi)型為“text”的<input>元素值將會(huì)綁定到目標(biāo)Action方法的字符串參數(shù)array。它們通過(guò)基于字符串的索引進(jìn)行命名,而作為索引的字符串通過(guò)類(lèi)型為“hidden”的<input>元素和作為參數(shù)綁定的數(shù)據(jù)一并提交。這些用于定義索引字符串的<input>元素一并命名為“index”。
現(xiàn)在我們對(duì)用于模擬默認(rèn)Model綁定的自定義DefaultModelBinder進(jìn)行進(jìn)一步完善,使之支持基于索引的數(shù)組綁定。如下的代碼片斷所示,我們?cè)谟糜谶M(jìn)行數(shù)組綁定的BindArrayModel方法中添加了額外的代碼用于提取索引值(整型和字符串類(lèi)型)列表,并且根據(jù)這行索引值生成相應(yīng)的前綴和對(duì)應(yīng)的Key通過(guò)ValueProvider得到針對(duì)數(shù)組元素的值。得到的值被添加到預(yù)先創(chuàng)建的對(duì)象列表中并最終成為作為參數(shù)值的數(shù)組對(duì)象的元素。
1: public class DefaultModelBinder
2: {
3: //其他成員
4: public object BindModel(Type parameterType, string prefix)
5: {
6: if (!this.ValueProvider.ContainsPrefix(prefix))
7: {
8: return null;
9: }
10:
11: ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType);
12: if (!modelMetadata.IsComplexType)
13: {
14: return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);
15: }
16: if (parameterType.IsArray)
17: {
18: return BindArrayModel(parameterType, prefix);
19: }
20: object model = CreateModel(parameterType);
21: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))
22: {
23: string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;
24: property.SetValue(model, BindModel(property.PropertyType, key));
25: }
26: return model;
27: }
28:
29: private object BindArrayModel(Type parameterType, string prefix)
30: {
31: List<object> list = new List<object>();
32: if (!string.IsNullOrEmpty(prefix) && this.ValueProvider.ContainsPrefix(prefix))
33: {
34: IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(parameterType) as IEnumerable;
35: if (null != enumerable)
36: {
37: foreach (var value in enumerable)
38: {
39: list.Add(value);
40: }
41: }
42: }
43:
44: bool numericIndex;
45: IEnumerable<string> indexes = GetIndexes(prefix, out numericIndex);
46: foreach (var index in indexes)
47: {
48: string indexPrefix = prefix + "[" + index + "]";
49: if (!this.ValueProvider.ContainsPrefix(indexPrefix) && numericIndex)
50: {
51: break;
52: }
53: list.Add(BindModel(parameterType.GetElementType(), indexPrefix));
54: }
55: object[] array = (object[])Array.CreateInstance(parameterType.GetElementType(), list.Count);
56: list.CopyTo(array);
57: return array;
58: }
59: private IEnumerable<string> GetIndexes(string prefix, out bool numericIndex)
60: {
61: string key = string.IsNullOrEmpty(prefix)?"index": prefix+"."+"index";
62: ValueProviderResult result = this.ValueProvider.GetValue(key);
63: if (null != result)
64: {
65: string[] indexes = result.ConvertTo(typeof(string[])) as string[];
66: if (null != indexes)
67: {
68: numericIndex = false;
69: return indexes;
70: }
71: }
72: numericIndex = true;
73: return GetZeroBasedIndexes();
74: }
75: private static IEnumerable<string> GetZeroBasedIndexes()
76: {
77: int iteratorVariable0 = 0;
78: while (true)
79: {
80: yield return iteratorVariable0.ToString();
81: iteratorVariable0++;
82: }
83: }
84: }
索引列表的獲取通過(guò)方法GetIndexes實(shí)現(xiàn)。由于作為索引值的數(shù)據(jù)項(xiàng)以“index”命名,所以該方法在此基礎(chǔ)上加上傳入的前綴作為key調(diào)用ValueProvider的GetValue方法可以直接得到針對(duì)指定前綴的所有字符串類(lèi)型的索引值。而針對(duì)基零整數(shù)的索引列表則通過(guò)GetZeroBasedIndexes方法返回。
我們現(xiàn)在將自定義的DefaultModelBinder用于進(jìn)行基于數(shù)組的Model綁定,在之前演示實(shí)例的基礎(chǔ)上我們對(duì)Action方法作了如下的修改,使之具有一個(gè)Contact數(shù)組類(lèi)型的參數(shù)。在該Action方法中,我們將作為數(shù)組元素的Contact對(duì)象相關(guān)信息呈現(xiàn)出來(lái)。對(duì)于通過(guò)GetValueProvider方法提供的NameValueCollectionValueProvider來(lái)說(shuō),我們以基零整數(shù)的方式提供了兩個(gè)Contact對(duì)象的數(shù)據(jù)。
1: public class HomeController : Controller
2: {
3: //其他成員
4: private IValueProvider GetValueProvider()
5: {
6: NameValueCollection requestData = new NameValueCollection();
7:
8: requestData.Add("[0].Name", "Foo");
9: requestData.Add("[0].PhoneNo", "123456789");
10: requestData.Add("[0].EmailAddress", "Foo@gmail.com");
11:
12: requestData.Add("[1].Name", "Bar");
13: requestData.Add("[1].PhoneNo", "987654321");
14: requestData.Add("[1].EmailAddress", "Bar@gmail.com");
15:
16: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
17: }
18:
19: public void Action(Contact[] contacts)
20: {
21: foreach (Contact contact in contacts)
22: {
23: Response.Write(string.Format("{0}: {1}<br/>", "Name", contact.Name));
24: Response.Write(string.Format("{0}: {1}<br/>", "PhoneNo", contact.PhoneNo));
25: Response.Write(string.Format("{0}: {1}<br/><br/>", "EmailAddress", contact.EmailAddress));
26: }
27: }
28: }
運(yùn)行我們的程序之后會(huì)在瀏覽器中得到如下所示的輸出結(jié)果,可見(jiàn)目標(biāo)Action的數(shù)組參數(shù)通過(guò)我們自定義的DefaultModelBinder得到了正確地綁定。(S517)
1: Name: Foo
2: PhoneNo: 123456789
3: EmailAddress: Foo@gmail.com
4:
5: Name: Bar
6: PhoneNo: 987654321
7: EmailAddress: Bar@gmail.com
上面這個(gè)例子演示了針對(duì)基零整數(shù)作為索引的數(shù)組綁定,DefaultModelBinder同樣支持針對(duì)任意字符串作為索引的數(shù)組綁定。在下面的代碼片斷中,我們修改了GetValueProvider方法使創(chuàng)建的NameValueCollectionValueProvider以字符串索引的方式為Contact數(shù)組提供數(shù)據(jù)。程序運(yùn)行之后,我們可以在瀏覽器中得到相同的輸出結(jié)果。
1: public class HomeController : Controller
2: {
3: //其他成員
4: private IValueProvider GetValueProvider()
5: {
6: NameValueCollection requestData = new NameValueCollection();
7: requestData.Add("index", "first");
8: requestData.Add("index", "second");
9:
10: requestData.Add("[first].Name", "Foo");
11: requestData.Add("[first].PhoneNo", "123456789");
12: requestData.Add("[first].EmailAddress", "Foo@gmail.com");
13:
14: requestData.Add("[second].Name", "Bar");
15: requestData.Add("[second].PhoneNo", "987654321");
16: requestData.Add("[second].EmailAddress", "Bar@gmail.com");
17:
18: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
19: }
20: }
通過(guò)實(shí)例模擬ASP.NET MVC的Model綁定的機(jī)制:簡(jiǎn)單類(lèi)型+復(fù)雜類(lèi)型
通過(guò)實(shí)例模擬ASP.NET MVC的Model綁定的機(jī)制:數(shù)組
通過(guò)實(shí)例模擬ASP.NET MVC的Model綁定的機(jī)制:集合+字典


基于數(shù)組和集合類(lèi)型的Model綁定機(jī)制比較類(lèi)似,對(duì)于綁定參數(shù)類(lèi)型或者參數(shù)類(lèi)型的某個(gè)屬性為數(shù)組或者集合,如果ValueProvider根據(jù)對(duì)應(yīng)的Key能夠匹配多條數(shù)據(jù),那么這些數(shù)據(jù)最終將會(huì)轉(zhuǎn)換為綁定的數(shù)組/集合的元素。此外,針對(duì)數(shù)組/集合的Model綁定還支持基于索引的方式。
浙公網(wǎng)安備 33010602011771號(hào)