好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

在本系列的前面两篇文章(《 简单类型+复杂类型 》、《 数组 》)我们通过创建的实例程序模拟了ASP.NET MVC默认使用的DefaultModelBinder对简单类型、复杂类型以及数组对象的Model绑定。现在我们按照相同的方式来分析基于集合和字典类型的Model绑定是如何实现的。[源代码从 这里 下载][本文已经同步到《 How ASP.NET MVC Works? 》中]

一、集合

这里的集合指的是除数组和字典之外的所有实现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( "&nbsp;&nbsp;&nbsp;&nbsp;{0}: {1}<br/>" , "Name" , contact.Name));
   26:              Response.Write( string .Format( "&nbsp;&nbsp;&nbsp;&nbsp;{0}: {1}<br/>" , "PhoneNo" , contact.PhoneNo));
   27:              Response.Write( string .Format( "&nbsp;&nbsp;&nbsp;&nbsp;{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

通过实例模拟ASP.NET MVC的Model绑定的机制:简单类型+复杂类型  
通过实例模拟ASP.NET MVC的Model绑定的机制:数组  
通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典

作者: Artech
出处: http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于通过实例模拟ASP.NET MVC的Model绑定的机制:集合+字典的详细内容...

  阅读:144次