您的位置:首页 > 编程语言 > ASP

通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典

2012-05-31 09:22 891 查看
[code] public class DefaultModelBinder

{

//其他成员

public object BindModel(Type parameterType, string prefix)

{

if (!this.ValueProvider.ContainsPrefix(prefix))

{

return null;

}

ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType);

if (!modelMetadata.IsComplexType)

{

return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);

}

if (parameterType.IsArray)

{

return BindArrayModel(parameterType, prefix);

}

object model = CreateModel(parameterType);

Type enumerableType = ExtractGenericInterface(parameterType, typeof(IEnumerable<>));

if (null != enumerableType)

{

return BindCollectionModel(prefix, model, enumerableType);

}

foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))

{

string key = prefix == "" ? property.Name : prefix + "." + property.Name;

property.SetValue(model, BindModel(property.PropertyType, key));

}

return model;

}


 private object BindCollectionModel(string prefix, object model, Type enumerableType)

{

 List<object> list = new List<object>();

 bool numericIndex;

 IEnumerable<string> indexes = GetIndexes(prefix, out numericIndex);

 Type elementType = enumerableType.GetGenericArguments()[0];


 if (!string.IsNullOrEmpty(prefix) && this.ValueProvider.ContainsPrefix(prefix))

{

 IEnumerable enumerable = this.ValueProvider.GetValue(prefix).ConvertTo(enumerableType) as IEnumerable;

 if (null != enumerable)

{

 foreach (var value in enumerable)

{

 list.Add(value);

}

}

}

 foreach (var index in indexes)

{

 string indexPrefix = prefix + "[" + index + "]";

 if (!this.ValueProvider.ContainsPrefix(indexPrefix) && numericIndex)

{

 break;

}

 list.Add(BindModel(elementType, indexPrefix));

}

 if (list.Count == 0)

{

return null;

}

 ReplaceHelper.ReplaceCollection(elementType, model, list);

return model;

}

 

 private Type ExtractGenericInterface(Type queryType, Type interfaceType)

{

 Func<Type, bool> predicate = t => t.IsGenericType && (t.GetGenericTypeDefinition() == interfaceType);

 if (!predicate(queryType))

{

 return queryType.GetInterfaces().FirstOrDefault<Type>(predicate);

}

 return queryType;

}

}

[/code]
[/code]
如上面的代码片断所示,在BindModel方法中我们通过调用ExtractGenericInterface判断目标类型是否实现了IEnumerable<T>接口,如果实现了该接口则提取泛型元素类型。针对集合的Model绑定实现在方法BindCollectionModel中,我们按照数组绑定的方式得的针对目标集合对象的所有元素对象,并将其添加到一个List<object>对象中,然后调用ReplaceHelper 的静态方法ReplaceCollection将该列表中的元素拷贝到预先创建的Model对象中。定义在ReplaceHelper的静态方法ReplaceCollection定义如下:

[code]
[code] internal static class ReplaceHelper

{

 private static MethodInfo replaceCollectionMethod = typeof(ReplaceHelper).GetMethod("ReplaceCollectionImpl", BindingFlags.Static |BindingFlags.NonPublic);


public static void ReplaceCollection(Type collectionType, object collection, object newContents)

{

 replaceCollectionMethod.MakeGenericMethod(new Type[]{ collectionType}).Invoke(null, new object[]{ collection, newContents});

} 

 private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents)

{

 collection.Clear();

 if (newContents != null)

{

 foreach (object obj2 in newContents)

{

 T item = (obj2 is T) ? ((T)obj2) : default(T);

 collection.Add(item);

}

}

}

}

[/code]
[/code]
为了让演示针对集合类型的Model绑定,我们对实例中的HomeController作了如下的修改。Action方法的参数类型替换成IEnumerable<Contact>,该集合中的每个Contact的信息在该方法中被呈现出来。通过GetValueProvider提供的NameValueCollectionValueProvider采用基零整数索引的方式定义数据项。

[code]
[code] public class HomeController : Controller

{

 private IValueProvider GetValueProvider()

{

 NameValueCollection requestData = new NameValueCollection();

 requestData.Add("[0].Name", "Foo");

 requestData.Add("[0].PhoneNo", "123456789");

 requestData.Add("[0].EmailAddress", "Foo@gmail.com");


 requestData.Add("[1].Name", "Bar");

 requestData.Add("[1].PhoneNo", "987654321");

 requestData.Add("[1].EmailAddress", "Bar@gmail.com");


 return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);

}


 public void Action(IEnumerable<Contact> contacts)

{

 foreach (Contact contact in contacts)

{

 Response.Write(string.Format("{0}:{1}<br/>", "Name", contact.Name));

 Response.Write(string.Format("{0}:{1}<br/>", "Phone No.", contact.PhoneNo));

 Response.Write(string.Format("{0}:{1}<br/><br/>", "Email Address",contact.EmailAddress));

}

}

}

[/code]
[/code]
该程序被执行之后,在浏览器上依然会呈现出如下所示的我们希望的数据,这充分证明了我们自定义的DefaultModelBinder具有针对集合的绑定能力。

[code]
[code] Name: Foo

PhoneNo: 123456789

EmailAddress: Foo@gmail.com


Name:Bar

PhoneNo: 987654321

EmailAddress:Bar@gmail.com

[/code]
[/code]

二、 字典

这里的字典指的是实现了接口IDictionary<TKey,TValue>的类型。在Model绑定过程中基于字典类型的数据映射很好理解,首先,字典是一个KeyValuePair<TKey,TValue>对象的集合,所以在字典元素这一级可以采用基于索引的匹配机制;其次,KeyValuePair<TKey,TValue>是一个复杂类型,可以按照属性名称(Key和Value)进行匹配。比如说作为某个ValueProvider数据源的NameValueCollection具有如下的结构,它可以映射为一个IDictionary<string, Contact>对象(Contact对象作为Value,其Name属性作为Key)。


[code]
[code] [0].Key : Foo

 [0].Value.Name: Foo

[0].Value.EmailAddress: Foo@gmail.com

 [0].Value.PhoneNo : 123456789


 [1].Key :Bar

 [1].Value.Name:Bar

[1].Value.EmailAddress:Bar@gmail.com

 [1].Value.PhoneNo : 987654321

[/code]
[/code]
现在我们对用于模拟默认Model绑定的自定义DefaultModelBinder作最后的完善,使之支持针对字典类型的Model绑定。如下面的代码片断所示,在通过调用CreateModel创建Model对象之后,我们调用ExtractGenericInterface方法判断目标类型是否是一个字典,如果是则返回具体的字典类型,然后调用BindDictionaryModel方法实施针对字典类型的Model绑定。


[code][code] public class DefaultModelBinder

{

//其他成员

public object BindModel(Type parameterType, string prefix)

{

if (!this.ValueProvider.ContainsPrefix(prefix))

{

return null;

}


ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType);

if (!modelMetadata.IsComplexType)

{

return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);

}

if (parameterType.IsArray)

{

return BindArrayModel(parameterType, prefix);

}

object model = CreateModel(parameterType);

 Type dictionaryType = ExtractGenericInterface(parameterType, typeof(IDictionary<,>));

 if (null != dictionaryType)

{

 return BindDictionaryModel(prefix, model, dictionaryType);

}


Type enumerableType = ExtractGenericInterface(parameterType, typeof(IEnumerable<>));

if (null != enumerableType)

{

return BindCollectionModel(prefix, model, enumerableType);

}

foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))

{

string key = prefix == "" ? property.Name : prefix + "." + property.Name;

property.SetValue(model, BindModel(property.PropertyType, key));

}

return model;

}

 

 private object BindDictionaryModel(string prefix, object model, Type dictionaryType)

{

 List<KeyValuePair<object, object>> list = new List<KeyValuePair<object, object>>();

 bool numericIndex;

 IEnumerable<string> indexes = GetIndexes(prefix, out numericIndex);

 Type[] genericArguments = dictionaryType.GetGenericArguments();

 Type keyType = genericArguments[0];

 Type valueType = genericArguments[1];


 foreach (var index in indexes)

{

 string indexPrefix = prefix + "[" + index + "]";

 if (!this.ValueProvider.ContainsPrefix(indexPrefix) && numericIndex)

{

 break;

}

 string keyPrefix = indexPrefix + ".Key";

 string valulePrefix = indexPrefix + ".Value";

 list.Add(new KeyValuePair<object, object>(BindModel(keyType, keyPrefix), BindModel(valueType, valulePrefix)));

}

 if (list.Count == 0)

{

return null;

}

 ReplaceHelper.ReplaceDictionary(keyType, valueType, model, list);

return model;

}

}

[/code]
[/code]
在BindDictionaryModel方法中,我们采用与数组/集合绑定一样的方式调用GetIndexes方法得到索引列表。在对该列表进行遍历过程中,我们在索引的基础上添加“.Key”和“.Value”后缀从而得到作为字典元素(KeyValuePair<TKey, TValue>)Key和Value对象的前缀,并将该前缀作为参数递归地调用BindModel方法得到具体作为Key和Value的对象。在得到字典元素Key和Value之后,我们创建一个KeyValuePair<object, object>对象并添加预先创建的列表中。最后我们调用ReplaceHelper的静态方法ReplaceDictionary将该列表拷贝到作为Model的字典对象中,ReplaceHelper的静态方法ReplaceDictionary定义如下。

[code]
[code] internal static class ReplaceHelper

{

//其他成员

 private static MethodInfo replaceDictionaryMethod = typeof(ReplaceHelper).GetMethod("ReplaceDictionaryImpl", BindingFlags.Static |BindingFlags.NonPublic);


 public static void ReplaceDictionary(Type keyType, Type valueType, object dictionary, object newContents)

{

 replaceDictionaryMethod.MakeGenericMethod(new Type[]{ keyType, valueType}).Invoke(null, new object[]{ dictionary, newContents});

}


 private static void ReplaceDictionaryImpl<TKey, TValue>(IDictionary<TKey, TValue> dictionary, IEnumerable<KeyValuePair<object, object>> newContents)

{

 dictionary.Clear();

 foreach (KeyValuePair<object, object> pair in newContents)

{

 TKey key = (TKey)pair.Key;

 TValue local2 = (TValue)((pair.Value is TValue) ? pair.Value : default(TValue));

 dictionary[key] = local2;

}

}

}

[/code]
[/code]
我们照例通过我们创建的实例程序来验证自定义的DefaultModelBinder是否能够支持针对字典的Model绑定。如下面的代码片断所示,我们让HomeController的Action方法接受一个IDictionary<string, Contact>类型的参数,并在该方法中将作为Key的字符串和作为Value的Contact的相关信息呈现出来。在GetValueProvider方法中提供的NameValueCollectionValueProvider按照相应的映射规则对绑定到字典对象的数据项。

[code]
[code] public class HomeController : Controller

{

 private IValueProvider GetValueProvider()

{

 NameValueCollection requestData = new NameValueCollection();

 requestData.Add("[0].Key", "Foo");

 requestData.Add("[0].Value.Name", "Foo");

 requestData.Add("[0].Value.PhoneNo", "123456789");

 requestData.Add("[0].Value.EmailAddress", "Foo@gmail.com");


 requestData.Add("[1].Key", "Bar");

 requestData.Add("[1].Value.Name", "Bar");

 requestData.Add("[1].Value.PhoneNo", "987654321");

 requestData.Add("[1].Value.EmailAddress", "Bar@gmail.com");


 return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);

}


 public void Action(IDictionary<string, Contact> contacts)

{

 foreach (string keyin contacts.Keys)

{

 Response.Write(key + "<br/>");

 Contact contact = contacts[key];

 Response.Write(string.Format("    {0}:{1}<br/>","Name", contact.Name));

 Response.Write(string.Format("    {0}:{1}<br/>","PhoneNo", contact.PhoneNo));

 Response.Write(string.Format("    {0}:{1}<br/><br/>", "EmailAddress", contact.EmailAddress));

}

}

}

[/code]
[/code]
程序运行之后会在浏览器中得到如下的我们期望的输出结果。(S520)

[code]
[code] Foo

 Name: Foo

PhoneNo: 123456789

EmailAddress: Foo@gmail.com


Bar

Name:Bar

PhoneNo: 987654321

EmailAddress:Bar@gmail.com

[/code]
[/code]
通过实例模拟ASP.NET MVC的Model绑定的机制:简单类型+复杂类型
通过实例模拟ASP.NET MVC的Model绑定的机制:数组
通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: