[續《通過實例模擬ASP.NET MVC的Model綁定機制:簡單類型+復雜類型]》]基於數組和集合類型的Model綁定機制比較類似,對於綁定參數類型或者參數類型的某個屬性為數組或者集合,如果ValueProvider根據對應的Key能夠匹配多條數據,那麼這些數據最終將會轉換為綁定的數組/集合的元素。此外,針對數組/集合的Model綁定還支持基於索引的方式。[源代碼從這裡下載]
一、基於名稱的數組綁定
對於針對NameValueConllectionProvider來說,通過GetValue方法得到的ValueProviderResult的RawValue總是一個字符串數組(不論是否具有多條數據於指定的Key相匹配,如果只有一條匹配的數據,RawValue就是一個具有一個元素的字符串數組)。當我們調用ValueProviderResult的ConvertTo方法將提供的值轉換成某種類型時,如果目標類型是數組或者集合,那麼RawValue代表的字符串數組元素將會轉換成目標對象的元素;如果目標類型不屬於集合,那麼參與數據轉換的僅僅是RawValue數組的第1個元素。
如下面的代碼片斷所示,在默認的HomeController的默認Action方法Index中,我們創建了一個NameValueCollectionValueProvider對象,作為數據源的NameValueCollection中包含了三個同名(foo)數據條目。我們調用它的GetValue方法得到一個ValueProviderResult對象,然後我們將該對象的RawValue呈現出來。最後我們調用該ValueProviderResult對象的ConvertTo對象將提供的值轉換為int[]和int,並將轉換後的值呈現出來。
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: }
運行這個程序之後,我們會在浏覽器中得到如下的輸出結果,上面針對NameValueConllectionProvider的論述可以從輸出結果中得到印證。
1: RawValue: System.String[]
2: ConvertTo(typeof(int[])): System.Int32[]
3: ConvertTo(typeof(int)): 123
NameValueConllectionProvider(FormValueProvider和QueryStringValueProvider)的數據值提供機制決定了Model綁定的默認行為。如果綁定的目標對象是一個數組或者集合,匹配的同名數據項將會作為目標對象的元素。實際上HttpFileCollectionValueProvider的數據值提供機制也類似,如果綁定的目標對象類型是一個HttpPostedFileBase數組,那麼匹配的同名文件輸入元素都將作為其數據源。
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" ... />
假設針對具有如下定義的Action方法ActionMethod提交的標單具有如上的輸入元素,在三個文本框中輸入的字符串將綁定到foo參數,而通過三個文件輸入元素上傳得文件將會綁定給bar參數。
1: Public void ActionMethod(string[] foo, HttpPostedFileBase[] bar)
現在我們對用於模擬默認Model綁定的自定義DefaultModelBinder進行進一步完善,使之對基於名稱的數組綁定提供支持。如下面的代碼片斷所示,我們在BindModel方法中添加了針對數組類型的Model綁定代碼,而具體的實現定義在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: }