ASP.NET MVC Model字典绑定
在《ASP.NET MVC Model简单复杂类型绑定》、《ASP.NET MVC Model数组绑定》通过创建的实例程序模拟了ASP.NET MVC默认使用的DefaultModelBinder对简单类型、复杂类型以及数组对象的Model绑定。现在我们按照相同的方式来分析基于集合和字典类型的Model绑定是如何实现的。[源代码从这里下载]
一、集合
这里的集合指的是除数组和字典之外的所有实现IEnumerable<T>接口的类型。和基于数组的Model绑定类似,ValueProvider可以将多个同名的数据项作为集合的元素,基于索引(基零整数和字符串)的数据项命名方式同样适用。我们对自定义的DefaultModelBinder作了如下的完善使之支持集合类型的Model绑定。
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: ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType); 11: if (!modelMetadata.IsComplexType) 12: { 13: return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType); 14: } 15: if (parameterType.IsArray) 16: { 17: return BindArrayModel(parameterType, prefix); 18: } 19: object model = CreateModel(parameterType); 20: Type enumerableType = ExtractGenericInterface(parameterType, typeof(IEnumerable<>)); 21: if (null != enumerableType) 22: { 23: return BindCollectionModel(prefix, model, enumerableType); 24: } 25: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType)) 26: { 27: string key = prefix == "" ? property.Name : prefix + "." + property.Name; 28: property.SetValue(model, BindModel(property.PropertyType, key)); 29: } 30: return model; 31: } 32: 33: private object BindCollectionModel(string prefix, object model, Type enumerableType) 34: { 35: List<object> list = new List<object>(); 36: bool numericIndex; 37: IEnumerable<string> indexes = GetIndexes(prefix, out numericIndex); 38: Type elementType = enumerableType.GetGenericArguments()[0]; 39: 40: if (!string.IsNullOrEmpty(prefix) && this.ValueProvider.ContainsPrefix(prefix)) 41: { 42: IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(enumerableType) as IEnumerable; 43: if (null != enumerable) 44: { 45: foreach (var value in enumerable) 46: { 47: list.Add(value); 48: } 49: } 50: } 51: foreach (var index in indexes) 52: { 53: string indexPrefix = prefix + "[" + index + "]"; 54: if (!this.ValueProvider.ContainsPrefix(indexPrefix) && numericIndex) 55: { 56: break; 57: } 58: list.Add(BindModel(elementType, indexPrefix)); 59: } 60: if (list.Count == 0) 61: { 62: return null; 63: } 64: ReplaceHelper.ReplaceCollection(elementType, model, list); 65: return model; 66: } 67: 68: private Type ExtractGenericInterface(Type queryType, Type interfaceType) 69: { 70: Func<Type, bool> predicate = t => t.IsGenericType && (t.GetGenericTypeDefinition() == interfaceType); 71: if (!predicate(queryType)) 72: { 73: return queryType.GetInterfaces().FirstOrDefault<Type>(predicate); 74: } 75: return queryType; 76: } 77: }
如上面的代码片断所示,在BindModel方法中我们通过调用ExtractGenericInterface判断目标类型是否实现了 IEnumerable<T>接口,如果实现了该接口则提取泛型元素类型。针对集合的Model绑定实现在方法 BindCollectionModel中,我们按照数组绑定的方式得的针对目标集合对象的所有元素对象,并将其添加到一个 List<object>对象中,然后调用ReplaceHelper 的静态方法ReplaceCollection将该列表中的元素拷贝到预先创建的Model对象中。定义在ReplaceHelper的静态方法 ReplaceCollection定义如下:
1: internal static class ReplaceHelper 2: { 3: private static MethodInfo replaceCollectionMethod = typeof(ReplaceHelper).GetMethod("ReplaceCollectionImpl", BindingFlags.Static |BindingFlags.NonPublic); 4: 5: public static void ReplaceCollection(Type collectionType, object collection, object newContents) 6: { 7: replaceCollectionMethod.MakeGenericMethod(new Type[] { collectionType }).Invoke(null, new object[] { collection, newContents }); 8: } 9: private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents) 10: { 11: collection.Clear(); 12: if (newContents != null) 13: { 14: foreach (object obj2 in newContents) 15: { 16: T item = (obj2 is T) ? ((T)obj2) : default(T); 17: collection.Add(item); 18: } 19: } 20: } 21: }
为了让演示针对集合类型的Model绑定,我们对实例中的HomeController作了如下的修改。Action方法的参数类型替换成 IEnumerable<Contact>,该集合中的每个Contact的信息在该方法中被呈现出来。通过 GetValueProvider提供的NameValueCollectionValueProvider采用基零整数索引的方式定义数据项。
1: public class HomeController : Controller 2: { 3: private IValueProvider GetValueProvider() 4: { 5: NameValueCollection requestData = new NameValueCollection(); 6: requestData.Add("[0].Name", "Foo"); 7: requestData.Add("[0].PhoneNo", "123456789"); 8: requestData.Add("[0].EmailAddress", "Foo@gmail.com"); 9: 10: requestData.Add("[1].Name", "Bar"); 11: requestData.Add("[1].PhoneNo", "987654321"); 12: requestData.Add("[1].EmailAddress", "Bar@gmail.com"); 13: 14: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture); 15: } 16: 17: public void Action(IEnumerable<Contact> contacts) 18: { 19: foreach (Contact contact in contacts) 20: { 21: Response.Write(string.Format("{0}: {1}<br/>", "Name", contact.Name)); 22: Response.Write(string.Format("{0}: {1}<br/>", "Phone No.", contact.PhoneNo)); 23: Response.Write(string.Format("{0}: {1}<br/><br/>", "Email Address",contact.EmailAddress)); 24: } 25: } 26: }
该程序被执行之后,在浏览器上依然会呈现出如下所示的我们希望的数据,这充分证明了我们自定义的DefaultModelBinder具有针对集合的绑定能力。
1: Name: Foo
2: PhoneNo: 123456789
3: EmailAddress: Foo@gmail.com
4:
5: Name: Bar
6: PhoneNo: 987654321
7: EmailAddress: Bar@gmail.com
二、 字典
这里的字典指的是实现了接口IDictionary<TKey,TValue>的类型。在Model绑定过程中基于字典类型的数据映射 很好理解,首先,字典是一个KeyValuePair<TKey,TValue>对象的集合,所以在字典元素这一级可以采用基于索引的匹配机 制;其次,KeyValuePair<TKey,TValue>是一个复杂类型,可以按照属性名称(Key和Value)进行匹配。比如说作 为某个ValueProvider数据源的NameValueCollection具有如下的结构,它可以映射为一个 IDictionary<string, Contact>对象(Contact对象作为Value,其Name属性作为Key)。
1: [0].Key : Foo
2: [0].Value.Name : Foo
3: [0].Value.EmailAddress: Foo@gmail.com
4: [0].Value.PhoneNo : 123456789
5:
6: [1].Key : Bar
7: [1].Value.Name : Bar
8: [1].Value.EmailAddress: Bar@gmail.com
9: [1].Value.PhoneNo : 987654321
现在我们对用于模拟默认Model绑定的自定义DefaultModelBinder作最后的完善,使之支持针对字典类型的Model绑定。如下面 的代码片断所示,在通过调用CreateModel创建Model对象之后,我们调用ExtractGenericInterface方法判断目标类型是 否是一个字典,如果是则返回具体的字典类型,然后调用BindDictionaryModel方法实施针对字典类型的Model绑定。
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: Type dictionaryType = ExtractGenericInterface(parameterType, typeof(IDictionary<,>)); 22: if (null != dictionaryType) 23: { 24: return BindDictionaryModel(prefix, model, dictionaryType); 25: } 26: 27: Type enumerableType = ExtractGenericInterface(parameterType, typeof(IEnumerable<>)); 28: if (null != enumerableType) 29: { 30: return BindCollectionModel(prefix, model, enumerableType); 31: } 32: foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType)) 33: { 34: string key = prefix == "" ? property.Name : prefix + "." + property.Name; 35: property.SetValue(model, BindModel(property.PropertyType, key)); 36: } 37: return model; 38: } 39: 40: private object BindDictionaryModel(string prefix, object model, Type dictionaryType) 41: { 42: List<KeyValuePair<object, object>> list = new List<KeyValuePair<object, object>>(); 43: bool numericIndex; 44: IEnumerable<string> indexes = GetIndexes(prefix, out numericIndex); 45: Type[] genericArguments = dictionaryType.GetGenericArguments(); 46: Type keyType = genericArguments[0]; 47: Type valueType = genericArguments[1]; 48: 49: foreach (var index in indexes) 50: { 51: string indexPrefix = prefix + "[" + index + "]"; 52: if (!this.ValueProvider.ContainsPrefix(indexPrefix) && numericIndex) 53: { 54: break; 55: } 56: string keyPrefix = indexPrefix + ".Key"; 57: string valulePrefix = indexPrefix + ".Value"; 58: list.Add(new KeyValuePair<object, object>(BindModel(keyType, keyPrefix), BindModel(valueType, valulePrefix))); 59: } 60: if (list.Count == 0) 61: { 62: return null; 63: } 64: ReplaceHelper.ReplaceDictionary(keyType, valueType, model, list); 65: return model; 66: } 67: }
在BindDictionaryModel方法中,我们采用与数组/集合绑定一样的方式调用GetIndexes方法得到索引列表。在对该列表进行 遍历过程中,我们在索引的基础上添加“.Key”和“.Value”后缀从而得到作为字典元素(KeyValuePair<TKey, TValue>)Key和Value对象的前缀,并将该前缀作为参数递归地调用BindModel方法得到具体作为Key和Value的对象。在得 到字典元素Key和Value之后,我们创建一个KeyValuePair<object, object>对象并添加预先创建的列表中。最后我们调用ReplaceHelper的静态方法ReplaceDictionary将该列表拷贝到 作为Model的字典对象中,ReplaceHelper的静态方法ReplaceDictionary定义如下。
1: internal static class ReplaceHelper 2: { 3: //其他成员 4: private static MethodInfo replaceDictionaryMethod = typeof(ReplaceHelper).GetMethod("ReplaceDictionaryImpl", BindingFlags.Static |BindingFlags.NonPublic); 5: 6: public static void ReplaceDictionary(Type keyType, Type valueType, object dictionary, object newContents) 7: { 8: replaceDictionaryMethod.MakeGenericMethod(new Type[] { keyType, valueType }).Invoke(null, new object[] { dictionary, newContents }); 9: } 10: 11: private static void ReplaceDictionaryImpl<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<object, object>> newContents) 12: { 13: dictionary.Clear(); 14: foreach (KeyValuePair<object, object> pair in newContents) 15: { 16: TKey key = (TKey)pair.Key; 17: TValue local2 = (TValue)((pair.Value is TValue) ? pair.Value : default(TValue)); 18: dictionary[key] = local2; 19: } 20: } 21: }
我们照例通过我们创建的实例程序来验证自定义的DefaultModelBinder是否能够支持针对字典的Model绑定。如下面的代码片断所 示,我们让HomeController的Action方法接受一个IDictionary<string, Contact>类型的参数,并在该方法中将作为Key的字符串和作为Value的Contact的相关信息呈现出来。在 GetValueProvider方法中提供的NameValueCollectionValueProvider按照相应的映射规则对绑定到字典对象的 数据项。
1: public class HomeController : Controller 2: { 3: private IValueProvider GetValueProvider() 4: { 5: NameValueCollection requestData = new NameValueCollection(); 6: requestData.Add("[0].Key", "Foo"); 7: requestData.Add("[0].Value.Name", "Foo"); 8: requestData.Add("[0].Value.PhoneNo", "123456789"); 9: requestData.Add("[0].Value.EmailAddress", "Foo@gmail.com"); 10: 11: requestData.Add("[1].Key", "Bar"); 12: requestData.Add("[1].Value.Name", "Bar"); 13: requestData.Add("[1].Value.PhoneNo", "987654321"); 14: requestData.Add("[1].Value.EmailAddress", "Bar@gmail.com"); 15: 16: return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture); 17: } 18: 19: public void Action(IDictionary<string, Contact> contacts) 20: { 21: foreach (string key in contacts.Keys) 22: { 23: Response.Write(key + "<br/>"); 24: Contact contact = contacts[key]; 25: Response.Write(string.Format(" {0}: {1}<br/>","Name", contact.Name)); 26: Response.Write(string.Format(" {0}: {1}<br/>","PhoneNo", contact.PhoneNo)); 27: Response.Write(string.Format(" {0}: {1}<br/><br/>", "EmailAddress", contact.EmailAddress)); 28: } 29: } 30: }
程序运行之后会在浏览器中得到如下的我们期望的输出结果。(S520)
1: Foo
2: Name: Foo
3: PhoneNo: 123456789
4: EmailAddress: Foo@gmail.com
5:
6: Bar
7: Name: Bar
8: PhoneNo: 987654321
9: EmailAddress: Bar@gmail.com
作者:Artech
出处:http://artech.cnblogs.com/
加支付宝好友偷能量挖...