当前位置: 代码迷 >> ASP >> asp.net mvc源码分析-DefaultModelBinder 聚合绑定
  详细解决方案

asp.net mvc源码分析-DefaultModelBinder 聚合绑定

热度:401   发布时间:2012-11-23 00:03:43.0
asp.net mvc源码分析-DefaultModelBinder 集合绑定

接着上篇关于数据绑定的asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证里面只讲了复杂数据类型的绑定,还有上面集合、字典等这些数据这么绑定的了。说到集合绑定其实网上已经有很多关于它的介绍通过实例模拟ASP.NET MVC的Model绑定机制:数组。这个我先举一个使用例子吧:

后端代码:


前端代码:

运行结果:


好,现在让我们来看看集合的数据究竟是怎么绑定的吧:

在BindComplexModel方法中有这么一段:

  Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof(IEnumerable<>));
            if (enumerableType != null) {
                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;
                }
            }

在这里TypeHelpers.ExtractGenericInterface方法主要是用来检查modelType是否是一个集合类型,Type elementType = enumerableType.GetGenericArguments()[0];这句就是获取集合元素类型,在这个例子中它是UserInfo的类型。  Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);这句也很好理解,就是创建一个elementType的集合类型,后面再看看当前的model是否是这个集合类型的实例,要注意一下默认这里的model是没有什么实际内容的,但是它也不为null,后面紧接着创建新的ModelBindingContext,最后调用UpdateCollection方法,看来绑定的关键还是在UpdateCollection方法里面:

internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
            bool stopOnIndexNotFound;
            IEnumerable<string> indexes;
            GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes);
            IModelBinder elementBinder = Binders.GetBinder(elementType);

            // build up a list of items from the request
            List<object> modelList = new List<object>();
            foreach (string currentIndex in indexes) {
                string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
                if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey)) {
                    if (stopOnIndexNotFound) {
                        // we ran out of elements to pull
                        break;
                    }
                    else {
                        continue;
                    }
                }

                ModelBindingContext innerContext = new ModelBindingContext() {
                    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, elementType),
                    ModelName = subIndexKey,
                    ModelState = bindingContext.ModelState,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
                };
                object thisElement = elementBinder.BindModel(controllerContext, innerContext);

                // we need to merge model errors up
                AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
                modelList.Add(thisElement);
            }

            // if there weren't any elements at all in the request, just return
            if (modelList.Count == 0) {
                return null;
            }

            // replace the original collection
            object collection = bindingContext.Model;
            CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
            return collection;
        }
     private static void GetIndexes(ModelBindingContext bindingContext, out bool stopOnIndexNotFound, out IEnumerable<string> indexes) {
            string indexKey = CreateSubPropertyName(bindingContext.ModelName, "index");
            ValueProviderResult vpResult = bindingContext.ValueProvider.GetValue(indexKey);

            if (vpResult != null) {
                string[] indexesArray = vpResult.ConvertTo(typeof(string[])) as string[];
                if (indexesArray != null) {
                    stopOnIndexNotFound = false;
                    indexes = indexesArray;
                    return;
                }
            }

            // just use a simple zero-based system
            stopOnIndexNotFound = true;
            indexes = GetZeroBasedIndexes();
        }
       private static IEnumerable<string> GetZeroBasedIndexes() {
            for (int i = 0; ; i++) {
                yield return i.ToString(CultureInfo.InvariantCulture);
            }
        }
首先我们看看 GetIndexes(bindingContext, out stopOnIndexNotFound, out indexes);它是干什么的?GetIndexes这个方法就是用来返回一个数组下标的方法,首先在bindingContext.ValueProvider.GetValue("index")找是否有值,如果有且能转换为string[],则返回,否则调用GetZeroBasedIndexes方法。接下来调用 IModelBinder elementBinder = Binders.GetBinder(elementType);来获取ModelBinder,后面就循环绑定集合中的每个元素。

 string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
                if (!bindingContext.ValueProvider.ContainsPrefix(subIndexKey)) {
                    if (stopOnIndexNotFound) {
                        // we ran out of elements to pull
                        break;
                    }
                    else {
                        continue;
                    }
                }

我们知道这里的bindingContext.ModelName默认是空字符,所以subIndexKey默认就是前面的indexes集合,如果前面indexes是通过GetZeroBasedIndexes方法获得的话,那么这里的subIndexKey应该是[0],[1]...[n]中间必须是连续的,当ValueProvider.ContainsPrefix([n])返回false的时候,就退出循环(indexes是通过GetZeroBasedIndexes方法获得),后面就又是创建一个新的ModelBindingContext对象innerContext,然后再绑定对应元素的值  object thisElement = elementBinder.BindModel(controllerContext, innerContext);这个具体怎么绑定的我想大家都应该很清楚了吧,不多说了。

现在我们来看看   AddValueRequiredMessageToModelState(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);主要是验证数据的有效性,而这里默认是在thisElement为null的时候才去验证的。

现在我们的集合元素也绑定完了,如果modelList没有成员就直接还回;否者就把modelList的值复制到 bindingContext.Model中去。

现在看来集合元素的绑定还是比较简单的,前提是我们对内置的基本类型数据绑定流程很清楚,并且也知道我们复杂类型的绑定流程。

其实个人感觉mvc集合绑定有些复杂,自己写了一个比较简单的集合绑定

 public class CollectionModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            Type type = bindingContext.ModelType;
            object model = Activator.CreateInstance(bindingContext.ModelType);
            List<object> modelList = new List<object>();
            if (type.IsGenericType)
            {
                Type elementType = type.GetGenericArguments()[0];
                Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
                if (collectionType.IsInstanceOfType(model))
                {
                    NameValueCollection formData = HttpContext.Current.Request.Form;
                    List<string> keys = new List<string>();
                    foreach (var item in formData.Keys)
                    {
                        keys.Add(item.ToString());
                    }
                    foreach (PropertyInfo p in elementType.GetProperties())
                    {
                        List<string> usekeys = keys.Where(x => x.Contains(p.Name)).ToList();
                        while (modelList.Count < usekeys.Count)
                        {
                            object obj = Activator.CreateInstance(elementType);
                            modelList.Add(obj);
                        }
                        for (int i = 0; i < usekeys.Count; i++)
                        {
                            p.SetValue(modelList[i], formData[usekeys[i]], null);
                        }

                    }
                }
        
                ReplaceCollection(elementType, model, modelList);
                return model;
            }
            return null;
        }

        private static readonly MethodInfo _replaceCollectionMethod = typeof(CollectionModelBinder).GetMethod("ReplaceCollectionImpl", BindingFlags.Static | BindingFlags.NonPublic);


        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        public static void ReplaceCollection(Type collectionType, object collection, object newContents)
        {
            MethodInfo targetMethod = _replaceCollectionMethod.MakeGenericMethod(collectionType);
            targetMethod.Invoke(null, new object[] { collection, newContents });
        }

        private static void ReplaceCollectionImpl<T>(ICollection<T> collection, IEnumerable newContents)
        {
            collection.Clear();
            if (newContents != null)
            {
                foreach (object item in newContents)
                {
                    // if the item was not a T, some conversion failed. the error message will be propagated,
                    // but in the meanwhile we need to make a placeholder element in the array.
                    T castItem = (item is T) ? (T)item : default(T);
                    collection.Add(castItem);
                }
            }
        }
    }
view调用不变,在Action调用的时候增加一个ModelBinder特性。  public ActionResult Index([ModelBinder(typeof(CollectionModelBinder))]List<UserInfo> users)