通过实例模拟ASP.NET MVC的Model绑定机制:数组2012-09-19 cnblogs Artech[续《通过实例模拟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: }