zoukankan      html  css  js  c++  java
  • Asp.net mvc 中Action 方法的执行(二)

      前面介绍了 Action 执行过程中的几个基本的组件,这里介绍 Action 方法的参数绑定。
      

    数据来源

      为 Action 方法提供参数绑定的原始数据来源于当前的 Http 请求,可能包含在请求的 Http 报文头部或报文体中,亦可能包含在当前请求的 Url 中。在 Asp.net mvc 中使用一个 IValueProvider 类型的对象来为 Action 方法的输入参数绑定提供原始的数据源。该接口定义如下:

    public interface IValueProvider{
    	//判断参数集合中的参数是否含有特定的前缀 prefix
    	bool ContainsPrefix(string prefix);
    	
    	//根据指定的 key获取对应的值
    	ValueProviderResult GetValue(string key);
    }
    

      接口中的第一个方法判断参数是否包含有某个特定的前缀,这是因为某些参数可能具有前缀,例如,现在具有一个类型 Person,该类型具有一个 Address 类型的属性,其具有 ProvinceCity 两个属性,这样类型 Person 的数据结构便不再是扁平化的了,而是具有了层次,如果要使用像字典这样的扁平化的结构进行存储,其示意图如下示:
      Alt text||center

      这里的数据便具有了前缀,其前缀便为 Address
    还有一种前缀,其形式类似于数组的索引,例如 Persons[0].Name
      接口中的第二个方法,根据指定的 Key 获取对应的 Value,其返回类型为 ValueProviderResult,该类型的主要成员如下:

    public class ValueProviderResult
        {
    	    //数据原始值的字符串形式
            public string AttemptedValue { get; protected set; }
    		
    		//文化信息,默认为 CultureInfo.InvariantCulture,一些如货币、时间等数据的具体形式与文化有关
            public CultureInfo Culture{ get;protected set;}
    		
    		//数据的原始值
            public object RawValue { get; protected set; }
    		
    		public ValueProviderResult(object rawValue, string attemptedValue, CultureInfo culture);
    		//原始的输入数据都是字符串形式的,下面的方法根据目标参数的元数据信息将其转换为对应的类型
            private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType);
           
            public object ConvertTo(Type type);
            
            public virtual object ConvertTo(Type type, CultureInfo culture);
        }
    

    IValueProvider 的实现类

    DictionaryValueProvider&ltT&gt

      DictionaryValueProvider 便是将 Dictionary 作为内部数据容器的 IValueProvider ,该类型的定义如下示(部分):

    public class DictionaryValueProvider<TValue> : IValueProvider, IEnumerableValueProvider
    {
    //内部存储容器
    private readonly Dictionary<string, ValueProviderResult> _values = new Dictionary<string, ValueProviderResult>(StringComparer.OrdinalIgnoreCase);
    public DictionaryValueProvider(IDictionary<string, TValue> dictionary, CultureInfo culture);
    
    //IValueProvider 接口方法
    public virtual bool ContainsPrefix(string prefix);
    
    //IValueProvider 接口方法
    public virtual ValueProviderResult GetValue(string key);
    }
    
    //IEnumerableValueProvider 接口方法
    public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix);//返回具有指定前缀的字典集合
    

      该类型具有三个子类,分别如下:

    • HttpFileCollectionValueProvider
        该类继承自 DictionaryValueProvider<HttpFileBase[]>,该类用于处理那些请求中上传的文件信息,该类的构造函数调用基类的构造函数,其 collection 参数是根据 Request.Files 所生成的 Dictionary<string,HttpPostedFileBase[]>,其 Key 为所上传的文件的名称。
        

    • RouteDataValueProvider
        该类继承自 DictionaryValueProvider<object>,其构造函数会调用基类的构造函数,collection 参数为 RouteData.Values

    • ChildActionValueProvider
        该类同样的继承自 DictinaryValueProvider<object>,用于处理 ChildAction 的数据。其构造函数亦是调用基类的构造函数,collection 参数同 RouteDataValueProvider 一样传递的是 RouteData.Values,但如果通过 HtmlHelper类的 Action 方法时对 RouteDataDictionary 类型的参数 routeValues 进行赋值了的话,这么这里的 collection 便为方法传入的 routeValues.

    NameValueCollectionValueProvider

      NameValueCollection 是一种类似于 Dictionary 的数据结构,不同的是其 Key 和 Value 均为字符串形式,且对 Key 不做唯一性的约束。NameValueCollectionValueProvider 便是将
    NameValueCollection 作为数据容器的 IValueProvider,该类型的定义如下示(部分):

    public class NameValueCollectionValueProvider : IValueProvider, IUnvalidatedValueProvider, IEnumerableValueProvider
    {
    	 public NameValueCollectionValueProvider(NameValueCollection collection, CultureInfo culture);
    
    	public NameValueCollectionValueProvider(
                                NameValueCollection collection, 
                                NameValueCollection unvalidatedCollection, 
                                CultureInfo culture, 
                                bool jQueryToMvcRequestNormalizationRequired
                                );
    }
    
    public virtual bool ContainsPrefix(string prefix);//判断当前键值集合的 Keys 中是否包含有指定前缀 prefix
    
    //接口方法
    
    //IValueProvider 接口方法,内部调用 GetValue(string key,bool skipValidation) 重载,第二个参数为false,表示进行数据的输入验证
    public virtual ValueProviderResult GetValue(string key);
    
    //IEnumerableValueProvider 接口方法
    public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix);//返回具有指定前缀的字典集合
    
    //IUnvalidatedValueProvider 接口方法
     public virtual ValueProviderResult GetValue(string key, bool skipValidation);//
    
    

      该类型同样的具有三个子类,分别如下:

    • FormValueProvider
        该类用于提供那些来自提交表单的数据(Request.Form)

    • QueryStringValueProvider
        该类用于提供那些来自于查询字符串(QueryString)的数据

    • JQueryFormValueProvider
        该类用于请求表单中的数据格式为 JQuery格式的,内部实现基本与 FormValueProvider 相同,都是内部仅定义了构造函数并调用了基类的构造函数,不同的是,其调用时 jQueryToMvcRequestNormalizationRequired 参数设置为 true,因此,会调用 string NormalizeJQueryToMvc(string key) 方法对数据的 Key进行处理,有关处理的进一步信息请移步
    ValueProviderCollection

      该类型表示 IValueProvider 类型的一个集合,该类的定义如下示:

    public class ValueProviderCollection : Collection<IValueProvider>, IValueProvider, IUnvalidatedValueProvider, IEnumerableValueProvider{
    	public ValueProviderCollection(IList<IValueProvider> list)
                : base(list){};
        
    //IValueProvider接口实现
     public virtual bool ContainsPrefix(string prefix);
     public virtual ValueProviderResult GetValue(string key);
    
    //IUnvalidatedValueProvider 接口实现
    public virtual ValueProviderResult GetValue(string key, bool skipValidation);
    
    //IEnumerableValueProvider 接口实现
    public virtual IDictionary<string, string> GetKeysFromPrefix(string prefix)
    
    //其它操作
    ....       
    }
    
    

      上述接口方法的调用,都会对当前集合中的所有的 IValueProvider 类型调用其同名的方法,例如,bool ContainsPrefix(string prefix),当集合中任意的 IValueProvider 的同名方法都返回 true 时,该方法便返回 true; 返回结果为 ValueProviderResult 的方法返回当前集合中第一个匹配的项。

    ValueProvider 的提供

      Asp.net mvc 中使用的 IValueProvider 类型对象都是通过 ValueProviderFactory 类型来提供的,该类型是一个抽象类,其定义如下示:

    public abstract class ValueProviderFactory
        {
            public abstract IValueProvider GetValueProvider(ControllerContext controllerContext);
        }
    

      该类型具有 7 个实现的子类,除了分别对应上述的 6 中具体的 IValueProvider外,还定义了一个 JSonValueProviderFactory类型,其 GetValueProvider 方法返回一个 DictionaryValueProvider<object> 类型的对象。在其内部,从 Request.InputStream 中读取提交的 Json 串,然后使用 JavaScriptSerializer 对其进行反序列化,然后通过前面说过的前缀的方式将其转换为平面化存储的字典的形式。

    ValueProviderFactoryCollection

      从名称上就看一看出这是一个 ValueProviderFactory 的工厂,该类的定义(部分)如下

    public class ValueProviderFactoryCollection : Collection<ValueProviderFactory>{
    
    //属性
    internal ValueProviderFactory[] CombinedItems;//内部容器,使用 _dependencyResolver 获取已注册的 ValueProviderFactory 对其进行初始化
    private IDependencyResolver _dependencyResolver;//Asp.net Mvc 内部 IOC 容器
    
    //构造函数
    public ValueProviderFactoryCollection();
    
    public ValueProviderFactoryCollection(IList<ValueProviderFactory> list): base(list)
    }
    
    internal ValueProviderFactoryCollection(IList<ValueProviderFactory> list, IDependencyResolver dependencyResolver)
                : base(list)
    
    //核心方法
    public IValueProvider GetValueProvider(ControllerContext controllerContext)
            {
                ValueProviderFactory[] current = CombinedItems;
                List<IValueProvider> providers = new List<IValueProvider>(current.Length);
                for (int i = 0; i < current.Length; i++)
                {
                    ValueProviderFactory factory = current[i];
                    IValueProvider provider = factory.GetValueProvider(controllerContext);
                    if (provider != null)
                    {
                        providers.Add(provider);
                    }
                }
                return new ValueProviderCollection(providers);
            }
    

      从上面的实现中可以看出,其 GetValueProvider 方法会调用当前集合中所有的 ValueProviderFactory 类型的同名方法,最终返回的是一个 ValueProviderCollection 对象,其中包含所有的不为 null 的 IValueProvider 类型对象。

      在 Asp.net mvc 中定义有一个叫做的 ValueProviderFactories 的类,在该类中实例化了一个 ValueProviderFactoryCollection 类型的属性 Factories,并使用上述的 7 中 ValueProviderFactory 类型实例集合对其进行了初始化,在 ControllerBase 中定义了一个 IValueProvider 类型的属性,该属性的初始化便是调用的该 Factories 属性的 IValueProvider GetValueProvider(ControllerContext controllerContext),也就是说这里的 IValueProvider类型的属性其实是一个 ValueProviderCollection 类型的实例,其中包含了 Asp.net mvc 默认内置的所有的 IValueProvider 类型实例。

      在开发过程中,如果需要自定义 IValueProvider 类型,便可以通过类似的方式进行添加和注册。即将自定义的 ValueProviderFactory 类型添加到 Factories 集合中。
      

    数据的绑定

      这里的数据绑定指的是从请求中提取出数据并将其赋值给 Action 方法的过程。在 Asp.net mvc 中数据的绑定是通过 IModelBinder 类型来实现的,该接口的定义如下示:

     public interface IModelBinder
        {
            object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
        }
    

      该方法的第一个参数类型为 ControllerCotext,表示当前处理的 Action 方法所在的 Controller 类的一些信息,可以看作是对 RequestContextControllerRouteData 三者的封装,第二个参数类型为 ModelBindingContext,表示当前绑定的上下文信息,该类定义的主要成员如下示:

     public class ModelBindingContext{
    //属性部分
    public object Model;//当前绑定的 Model 对象,如果绑定对象为属性,则为该属性的值,如果绑定对象为一个类型,则为该类型的一个实例。值来源于 ModelMetaData 的同名属性
    	
    public ModelMetadata ModelMetadata { get; set; }//当前绑定对象相关的元数据信息
    
    public string ModelName;//当前绑定对象的名称
    
    public ModelStateDictionary ModelState;//当前绑定的 Model 的状态信息
    
    public Type ModelType;//当前要绑定的 Model的类型,如果为属性则为改属性的类型
    
    public IDictionary<string, ModelMetadata> PropertyMetadata;当前要绑定的 model 的属性集合,其中key 为 属性的名称,值为描述该属性的 ModelMetadata
    
    public IValueProvider ValueProvider { get; set; }//为当前 model 绑定提供源数据的 IValueProvider
      }
    

    IModelBinder 的实现类

      在 Asp.net mvc 中内置了 5 个 IModelBinder 的实现类,如下示:

    • ByteArrayModelBinder(有一个直接的子类,LinqBinaryModelBinder)
    • CancellationTokenModelBinder
    • DefaultModelBinder
    • HttpPostedFileBaseModelBinder
    ByteArrayModelBinder

      ByteArrayModelBinderobject BindModel(ControllerContext controllerContext,BindingContext bindingContext) 方法返回一个字节数组,生成该字节数组的方式为,通过 bindingContextValueProvider 属性调用其 GetValue(string key) 方法返回一个 ValueProviderResult 类型的结果,如果该结果为 null 或该结果的 attemptValue 属性为空字符串,则返回 Null, 否则对该结果的 AttemptValue 使用 Base - 64 编码方式进行编码,返回编码后的字节数组。具体的使用场合未知。其子类 LinqBinaryModelBinderBindModel 方法中调用基类的同名方法,然后将基类返回的字节数组封装为了一个 Binary 类型并返回。

    CancellationTokenModelBinder

      该类型的 BindModel 方法返回一个用于取消异步操作的令牌 CancellationToken.使用场合未知

    HttpPostedFileBaseModelBinder

       该类型的 BindModel 方法返回的一个 HttpPostedFileBase 类型的对象,该对象来自于 Requrest.Files[key],其中的 key 为当前绑定的 Model 的名称,即 bindContext.ModelName,如果不存在指定的项,则返回 null。

    DefaultModelBinder

      这是 Asp.net mvc 内部默认使用的 IModelBinder,下面着重对该类进行说明,说之前需要明确一个概念,复杂类型简单类型,针对这两种不同的类型,内部采用的绑定的方式是不同的。

      判断一个类型是简单类型还是复杂类型的标准只有一个,那就是 --- 是否支持源自字符串的类型转换,内部是通过判断是否支持 TypeConverter.CanConvertFrom(typeof(string)) 类型定义的转换来探测的,所有的 基元类型 都是简单类型,所有的自定义类型都是复杂类型。描述绑定对象的 ModelMetaData 具有一个 bool 类型的属性 IsComplexType 用以指示当前的绑定类型是复杂类型还是简单类型。

    简单类型的绑定

      简单类型的绑定也相对简单一些,主体思路就是利用当前的 bindingContextValueProvider 获取要用于绑定的值并返回。简单类型的绑定主要实现在 object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) 方法中。
      绑定的逻辑有可以分为两种情况,第一种,最简单的情况,即当前要绑定的 Model 的类型与从 Request 中获取的原始数据(RawValue)类型一致(同为字符串),在这种情况下,直接返回 ValueProviderRawValue 属性值即可。
      第二种情况及时当前要绑定的 Model 类型与从 Request 中获取的原始数据的类型不一致,这是仍然有两种情况,第一种就是要绑定的类型为数组、泛型的 IEnumerable<T> 集合,第二种为绑定的类型是单个的非字符串的元素。
      描述当前绑定对象的 ModelMetaDatabool 类型的属性 IsArray 标示当前的绑定对象是否为数组,针对数组的绑定也比较简单,只需调用 ValueProviderResult 类型的 object ConvertTo(destinationType) 方法便可实现。对于单个的非字符串的单个元素的处理也很简单,与上述的数组处理的方式一致,不在赘述。
      针对泛型的 IEnumerable 的绑定要复杂一点,其实现如下所示:

    // 判断当前的 ModelType 是否为实现了泛型接口 IEnumerable<T>,并返回具体的 IEnumberable<T> 的类型,如 IEnumerable<System.Int32>
    
    Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));//该方法会首先判断两者的类型是否匹配,判断的依据为 type1 位泛型且 type1.GetGenericTypeDefinition() == type2,例如 List<int> 与 typeof(List<>),如果匹配则直接返回 type1,否则 从 type1的实现的接口中按照同样的规则找到匹配的类型,没有是返回 null
    
    if (enumerableType != null)
    {
    //绑定类型为 IEnumerable<T>
    // case 2: user asked for a collection rather than an array// need to call ConvertTo() on the array type, then copy the array to the collection
    object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
                        
    Type elementType = enumerableType.GetGenericArguments()[0];//获取泛型参数 T 的类型
    Type arrayType = elementType.MakeArrayType();//创建 T 的数组类型
    
    //从 valueProvider 中解析原始数据转转换为 arrayType                     
    object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
    
    Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
    if (collectionType.IsInstanceOfType(modelCollection))
    {
      //将从 ValueProviderResult 中获取的数据填充的创建的集合中
      CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
                        }
       return modelCollection;
                    }
    
    复杂类型的绑定

      复杂类型的绑定主要通过 object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 方法来实现,复杂类型的绑定是一个递归的过程,如果绑定对象为 null 的情况下,首先会通过反射的方式创建一个空的绑定对象,这个是通过 object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 方法来实现的,然后在逐步绑定对象的各个值。
      复杂类型的绑定类似于简单类型的绑定,也可以分为三种情况,

    • 第一种,绑定对象为普通的对象
    • 第二种,绑定对象为数组
    • 第三种,绑定对象为一些特殊的集合,如IDictionaryIEnumerableICollection等泛型的集合

      先来说最简单的一种,绑定的对象为单个的对象,在对对象的属性的进行绑定时还涉及一些属性的验证,如属性是否能够赋值,属性的类型验证等,这里不做说明。复杂类型的属性绑定主要是通过 void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) 方法来进行的,其中的最后一个参数便为预先创建的绑定对象,下面是该类的具体的实现:

    internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model)
            {
         //这里会判断当前的绑定对象上是否使用了 BindAttribute 特性,如果有会根据该特性对要绑定的进行过滤,因此要重新创建一个新的绑定上下文对象,对其 
          ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
    
        // validation
        if (OnModelUpdating(controllerContext, newBindingContext))//这个方法是一个虚方法,内部直接返回 True,并且DefaultModelBinder没有预定义的子类
        {
        //绑定属性
        BindProperties(controllerContext, newBindingContext);
    				
        //验证失败的属性添加错误信息,是否验证通过取决于与该属性相关的 ModelErrorCollection 类型的Erros 集合是否为空,为空则表示验证成功
       OnModelUpdated(controllerContext, newBindingContext);
                }
            }
    

      在 BindProperties 方法中,首先会获取定义在当前绑定对象上定义的属性的集合,然后对该属性集合进行遍历,过滤掉不能进行赋值的属性,对其它的属性进行绑定,绑定通过调用 void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) 方法来实现,该方法的主要代码如下示:

    
    //根据 绑定对象的名称及要进行绑定的属性的名称生成 key
    //具体的生成规则为,prefix 为空返回 PropertyName,propertyName 为空返回 prefix
    //否则返回 prefix + "." + propertyName
    string fullPropertyKey = CreateSubPropertyName(prefix:bindingContext.ModelName, propertyName:propertyDescriptor.Name);
    
    //不存在指定前缀的源数据则直接返回
    if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) {
        return;
    }
    
    //根据当前属性的类型获取对应的 IModelBinder
    IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
    
    object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
    
    //获取与当前绑定的属性相关的元数据描述对象
    ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
    propertyMetadata.Model = originalPropertyValue;
    
    //创建新的 ModelBindingContext 用于当前属性的绑定
    ModelBindingContext innerBindingContext = new ModelBindingContext() {
            ModelMetadata = propertyMetadata,
            ModelName = fullPropertyKey,
            ModelState = bindingContext.ModelState,
            ValueProvider = bindingContext.ValueProvider
    };
    
    //获取当前要绑定的属性的值
    object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);//获取值的方式便是调用前面生成的 propertyBinder 的 GetValue 方法
    propertyMetadata.Model = newPropertyValue;
    
    //Validation
    ....
    

      至此,第一种情况的绑定便完成了。
      第二种,绑定的对象为数组。这种情况下的绑定实现如下示:

    if (model == null && modelType.IsArray) {
        Type elementType = modelType.GetElementType(); //获取数组中的元素类型
        Type listType = typeof(List < > ).MakeGenericType(elementType); //创建此种 list 类型是为了方便统一调用
        object collection = CreateModel(controllerContext, bindingContext, listType);
    
        //创建新的 ModelBindingContext 用于 当前数组的绑定
        ModelBindingContext arrayBindingContext = new ModelBindingContext() {
              ....
        };
    	
    	//从请求中获取源数据 
        IList list = (IList) UpdateCollection(controllerContext, arrayBindingContext, elementType);
    
        if (list == null) {
            return null;
        }
    
        Array array = Array.CreateInstance(elementType, list.Count);
        list.CopyTo(array, 0);
        return array;
    }
    

      这里比较重要的就是在于如何从请求中获取源数据,从上面的代码中可以看出是通过调用 UpdateCollection 这个方法来实现的。在该方法中,首先会获取一个索引的集合,获取的方式便是创建一个 0 基的数组,数组的长度等于绑定对象数组的长度,数组中的元素为 0 ~ n - 1 的字符串,其中 n 位对象数组的长度,然对创建的索引集合进行遍历,遍历中创建用于从 IValueProvider 中获取数据的 Key, 创建的方式便为当前绑定对象的名称 name + [i],其中的 i 为上一步创建的索引结合中的元素,例如,当前绑定的对象为 Student[] students,则 创建的 key 便为 student[i],然后根据当前集合中的元素的类型获取 IModelBinder 类型的实例,创建一个新的 BindingContext 对象用于当前对象的绑定,调用 IModelBinderBindModel 方法并将返回结果添加到预先创建的 List<Object> 类型的集合 modelList 中,如果 modelList 为空返回null,否则将这个 modelList 封装为一个 Object 并返回。
      至此,第二种绑定对象为数组的绑定便完成了。
      第三种,有可以分为两种情况,第一种绑定对象为 IEunmerable 类型,其绑定的方式基本上与数组的绑定相同,其实现过程如下示:

    //提取绑定对象实现的泛型接口
    Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable < > ));
    if (enumerableType != null) 
    {
    //获取具体的泛型类型 T
        Type elementType = enumerableType.GetGenericArguments()[0];
    
        Type collectionType = typeof(ICollection < > ).MakeGenericType(elementType);
        if (collectionType.IsInstanceOfType(model)) {
            ModelBindingContext collectionBindingContext = new ModelBindingContext() {
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
                    ModelName = bindingContext.ModelName,
                    ModelState = bindingContext.ModelState,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
            };
            object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
            return collection;
        }
    }
    

      根据上面的数组的绑定过程,这里的过程就很容易看懂了,不在赘述。
      第二种情况时绑定对象为字典,实现过程如下示:

    //提取绑定对象的泛型接口类型
     Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IDictionary<,>));
                if (dictionaryType != null)
                {
                   //获取绑定对象具体的参数类型
                    Type[] genericArguments = dictionaryType.GetGenericArguments();
                    Type keyType = genericArguments[0];//获取 Key 的具体类型
                    Type valueType = genericArguments[1];// 获取 Value 的具体类型
    
                    ModelBindingContext dictionaryBindingContext = new ModelBindingContext()
                    {
                        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType),
                        ModelName = bindingContext.ModelName,
                        ModelState = bindingContext.ModelState,
                        PropertyFilter = bindingContext.PropertyFilter,
                        ValueProvider = bindingContext.ValueProvider
                    };
                    
    				//从 Request 中获取绑定数据
                    object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
                    return dictionary;
                }
    

      其中的 UpdateDiecionary 方法的作用同上面的 UpCollection,实现上的思路也是一致的,首先还是创建索引集合,调用的是和绑定对象为数组时相同的方法,预创建一List<KeyValuePair<object, object>> 类型的变量用于保存从请求中提取出的数据,然后对第一步得到的索引集合进行遍历,在遍历过程中,首先,创建用于获取索引项的 Key,形式如 data[index].key,然后根据当前字典参数中key 的类型和 value 类型获取对应的 IModelBinder 类型实例,第一步根据获取得到的data[index].key获取其对应的key 的值,然后在根据这个 key 值去获取该key对应的 value 的值。
      例如,现在有一个 ActionResult dicTest(Dictionary<string,Person> nv)的 Action 方法,通过 postman 发起请求调用该方法,传递如下的参数:

    {
      "nv": {
        "Tom": { "Name": "tom" },
        "Jarry": { "Name": "jarry" }
            }
    }
    

      那么,根据字典集合中的键和值的类型获取对应的 IModelBinder 类型的实例,对第一步获取的索引的集合进行遍历,首先生成用于获取 key 值的索引,如modelName[index].key,其中的 modelName 为为当前要绑定的参数的名称,index 即为上一步获取的索引,在这里便是 nv[index].key,然后 nv[index].key 去获取对应的key 的值,这里获取的 key 的值便为 nv.Tom 和 nv.Jarry,然后使用上一步获取的 key 的值去获取其对应的值,即 {"Name":"tom"}{"Name":"jarry"}.,生成 IKeyValuePari<key,value> 类型的对象,其key 为 "Tom",其 value 为 object 类型的 {"Name":"tom"},然后将其添加到之前创建的 List<KeyValuePair<object, object>> 类型的变量中去,最后在将其中的数据填充到请求的 参数中。

    IModelBinder 类型的获取

    IModelProvider 接口

      在该接口中仅定义了一个方法,如下示:

    IModelBinder GetBinder(Type modelType);//根据传入的类型获取何时的 IModelBinder 实例
    

      在 Asp.net mvc 内并没有提供实现该接口的实现,在 System.Web.Mvc 程序集下也只有 ModelBinderProviderCollection 一个类型引用了该接口。该类型继承自 Collection,该类型具有两个重载的构造函数,如下示:

    public ModelBinderProviderCollection(IList<IModelBinderProvider> list):base(list)
    
    internal ModelBinderProviderCollection(IList<IModelBinderProvider> list, IDependencyResolver dependencyResolver): base(list)
    

      第二个构造函数可接受一个 IDenpendencyResolver 类型,该类型可以看作是 Asp.net mvc 内置的一个 IOC 容器。但该构造函数的访问修饰符为 internal,也就是说在 System.Web.Mvc 程序集之外是无法调用的,该类型还有一个 IModelBinderProvinder[] 类型的属性 CombinedItems,该属性内部便是使用的上面传入的 IDenpendencyResolver 实例,如果该实例为null,则使用默认的 DefaultDenpendencyResolver 类型。
      该类型还定义了一个 public IModelBinder GetBinder(Type modelType) ,根据传入的类型返回具体的 IModelBinder 类型的实例,内部对上述的 CombinedItems 属性进行遍历,调用其内部 IModelBinderProvider 的同名方法。

    ModelBinders

      该类型是一个静态类型,该类型公开提供的仅有一个 ModelBinderDictionary 类型的静态属性 Binders,改属性的初始化是通过其内部的一个私有的静态方法来实现的,该方法如下示:

    private static ModelBinderDictionary CreateDefaultBinderDictionary() 
        ModelBinderDictionary binders = new ModelBinderDictionary()
         { 
    { typeof(HttpPostedFileBase), new HttpPostedFileBaseModelBinder() }, 
    { typeof(byte[]), new ByteArrayModelBinder() }, 
    { typeof(Binary), new LinqBinaryModelBinder() },
    { typeof(CancellationToken), new CancellationTokenModelBinder() }
        };
        return binders;
    }
    
    

      可以看到仅仅是传入了之前说的那几种内置的 IModelBinder 类型的实例

    ModelBinderProviders

      该类型同样是一个静态类,仅定义了一个 ModelBinderProviderCollection 类型的属性 BinderProviders,该属性初始化时仅调用了 ModelBinderProviderCollection 类型的无参构函数。

    ModelBinderDictionary

      该类型派生自 IDictionary<Type, IModelBinder>,该类型的主要成员如下示:

    //属性
    private readonly Dictionary<Type, IModelBinder> _innerDictionary = new Dictionary<Type, IModelBinder>();//该类公开了一些对该字典集合进行增加、清空、移除等操作的方法
    
    //构造函数
     public ModelBinderDictionary(): this(ModelBinderProviders.BinderProviders);
     internal ModelBinderDictionary(ModelBinderProviderCollection modelBinderProviders);
    
    //方法
    public IModelBinder GetBinder(Type modelType);//方法内部调用第二个重载(type,true)
    public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault);//其第二参数表示获取失败时是否使用默认的 IModelBinder,内部调用第三个重载((fallbackToDefault) ? DefaultBinder : null),其中的 DefaultBinder 便是之前介绍的 DefaultModelBinder 类型的实例
    
    private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder);
    

      最后一个重载,在根据传入的 Type 查找对应的 IModelBinder 类型时按照如下的查找

    1. 构造函数传入的 ModelBinderProviderCollection 调用其同名方法进行查找
    2. 在通过全局注册的表中进行查找,即在该类型定义的 Dictionary<Type, IModelBinder> 类型的属性 *_innerDictionary * 中进行查找
    3. 通过 CustomModelBinderAttribute 指定的 IModelBinder
    4. 使用默认值
        经过前面的3步,仍未查找到对应的结果,如果其 fallbackToDefault 参数为 False 时,则返回 null.否则返回默认的 DefaultModelBinder 类型的实例。

      大多数情况下,使用的 IModelBinder 类型都是通过该类型的 GetBinder 方法来获取的,一般都是通过 ModelBinder.Binders 属性来获取该类型的实例,然后调用其 GetBinder 方法。

      至此,Model 绑定相关的组件及其绑定便介绍完了。

  • 相关阅读:
    C++ string char[] 转化
    c++ 转化
    2014/4/16
    2014/4/11
    垂直电商现倒闭潮
    经典K线组合图解 > 正文
    上下影线
    分​析​主​力​试​盘​手​法
    nginx重新编译不停服
    nexus
  • 原文地址:https://www.cnblogs.com/ITusk/p/8329856.html
Copyright © 2011-2022 走看看