本文共 19237 字,大约阅读时间需要 64 分钟。
模型绑定存在的意义就是为Action的参数提供值,例如:如下表单中提交了数据,那么Action(即:Index)的参数Id,Name的值就是表单中对应的name属性相同的值,而表单提交的值是如何赋值给Action的参数的呢?模型绑定就是来完成从用户提交的请求中提取数据,并赋值给Action的参数。此例是从表单中的提取数据,并赋值给Action的参数,模型绑定还可以完成完成从地址Url、路由Route、上传文件等中获取数据,并赋值给Action相应的参数。
[HttpPost]//注意:参数名必须要和html标签中的name属性相同 public ActionResult Index(string Id,string Name) { return Content(Id+Name); } |
MVC中的模型绑定都是有默认的模型绑定DefaultModelBinder来完成,为清楚模型绑定的机制,我们来通过自定义模型绑定来由浅到深的学习
模型绑定整个过程可以分为:从请求中获取数据、将请求中的数据转换成Action参数的类型并返回。 模型绑定必须要实现IModelBinder接口,改接口中有唯一的返回值类型为object类型的方法BindModel,改方法的返回值就是相应的Action参数的值,那么可以这么理解,当接收到请求并在执行Action之前,要调用BindModel方法,在改方法的内部直接或简介的实现从请求中获取值,并返回给Action的参数。 BindModel方法的参数:ControllerContext是当前Controller的上下文,即:封装了当前Controller和Route的相关信息;而ModelBindingContext则是当前绑定的参数类型的相关信息,例如有这么一个Action:public ActionResult Index(User use),此时ModelBindingContext就是参数use的相关信息。public interface IModelBinder { object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); }
简单类型和复杂类型的模型绑定
示例1:模型绑定机制
public class MyModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return "获取的值并返回"; } }
[HttpPost] public ActionResult Index([ModelBinder(typeof(MyModelBinder))]string Temp1,[ModelBinder(typeof(MyModelBinder))]string Temp2) { return Content(Temp1+temp2); } //[ModelBinder(typeof(MyModelBinder))]表示此处string类型的参数Temp的值由自定义的MyModelBinder提供
此示例实现了对指定参数类型的模型绑定,在调用Action之前,对于每个参数都需要调用与之绑定的ModelBinder的BindModel方法来获取值,当然在我们的示例中,参数Temp1和Temp2各自都要执行一遍MyModelBinder的BindModel方法来获取相应的参数。
但是,我们的参数是在BinderModel方法中直接写的,而在实际中我们是要从用户发来的请求中获取到的,由此我们由引出一个叫做ValueProvider的组件,直译就是值提供器,通过它来实现在请求中获取值。值提供器需要实现IValueProvider接口,默认的值提供器有:FormValueProvider、RouteDataValueProvider、 QueryStringValueProvider、 HttpFileCollectionValueProvider,分别是从表单、路由、地址字符串、上传文件中获取数据,既然同时存在这么多的ValueProvider,那么他们的调用肯定是有顺序的(就是按照上面写的顺序啦啦啦啦...),值得说的是,只要在找到一个值,那么就不再继续在其它的ValueProvider中找了。 IVaueProvider接口public interface IValueProvider { bool ContainsPrefix(string prefix); ValueProviderResult GetValue(string key); }
ContainsPrefix方法用来判断是否包含前缀,GetValue方法则是用来获取值,并返回一个封装了获取的值和相关转换方法的ValueProviderResult类型。
示例2:利用默认的ValueProvider实现自定义模型绑定
//Html//Action [HttpPost] public ActionResult Index([ModelBinder(typeof(MyModelBinder))]string Temp) { return Content(Temp); } //自定义ModelBinder public class MyModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { IValueProvider valueProvider = bindingContext.ValueProvider; string key = bindingContext.ModelName; Type modelType = bindingContext.ModelType; if (valueProvider.ContainsPrefix(key)) { return valueProvider.GetValue(key).ConvertTo(modelType); } return null; } }Index
此示例利用默认的ValueProvider从表单中获取标签的name属性为Temp的Text的数值,在自定义的BindModel方法中可以看出ModelBindingContext的的重要性,bindingContext.ValueProvider得到值提供器,bindingContext.ModelName得到的是绑定的Action方法中的参数名(此例中为:Temp),bindingContext.ModelType得到的是绑定的参数的类型(此例中为:string),valueProvider.ContainsPrefix(“Temp”)就是在全部的表单中检查是否存在这样name属性为Temp的标签,valueProvider.GetValue(key).ConvertTo(modelType)就是获取值并转换为Action参数的类型!----注意:默认的ContainsPrefix方法中,只有表单中存在Temp或Temp.才返回true
示例3:利用自定义ValueProvider和自定义ModelBinder实现模型绑定
//自定义ModelBinder public class MyModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(bindingContext.ModelType); } }//自定义ValueProvider public class MyValueProvider:IValueProvider { public bool ContainsPrefix(string prefix) { //暂时不写判断是否含有前缀的定义,因为貌似默认情况下,不是是否含有此前缀,而是是否含有此关键字 return false; } public ValueProviderResult GetValue(string key) { string[] objResult = HttpContext.Current.Request.Form.GetValues(key);//获取表单中name属性是key的所有值,放入到一个字符串数组中 string strResult = HttpContext.Current.Request.Form[key]; //将原始值(数组),和值的字符串形式封装到一个ValueProviderResult中 ValueProviderResult vpr = new ValueProviderResult(objResult, strResult, System.Globalization.CultureInfo.CurrentCulture); return vpr; } } //自定义ValueProviderFactory public class MyValueProviderFactory:ValueProviderFactory { public override IValueProvider GetValueProvider(ControllerContext controllerContext) { //此处构造了无参数的构造函数 return new MyValueProvider(); } }//在程序启动时将自定义的ValueProvider添加到程序中 protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); //因为在ValueProvider中,只要没有找到响应的值就会向下进行,所以我们将其他的ValueProvider移除 ValueProviderFactories.Factories.RemoveAt(4); ValueProviderFactories.Factories.RemoveAt(3); ValueProviderFactories.Factories.RemoveAt(2); ValueProviderFactories.Factories.RemoveAt(1); ValueProviderFactories.Factories.RemoveAt(0); ValueProviderFactories.Factories.Insert(0,new MyValueProviderFactory()); //ValueProviderFactories.Factories.Add(new MyValueProviderFactory()); }
此例利用自定义的ValueProvider来从请求中的表单中获取数据,并封装到一个ValueProviderResult中,而在自定义的ModelBinder中调用valueProvider,并得到其返回的ValueProviderResult类型的值,然后再利用ValueProviderResult的ConvertTo方法,将获取到的string[]类型转换为相应的类型(即:GetValue方法返回的是一个string[]类型),最终完成模型的绑定。但是在这个例子中我们没有在自定义的ValueProvider中写ContainsPrefix方法,当然程序中也没有用到他去判断,而是直接去利用GetValue方法去获取表单中的值。那么下面就来实现这个方法,并在程序中利用!
示例4:自定义ModelBinder和自定义valueProvider(自己写ContainsPrefix方法)
上述的示例中都是对简单类型的模型绑定,下面我们就来写一个对复杂类型的绑定--->User类User类public class User{ public int ID{set;get;} public string Name{set;get;} }//前台//Action [HttpPost] public ActionResult Index([ModelBinder(typeof(MyModelBinder))]User use, [ModelBinder(typeof(MyModelBinder))]User uu) { return Content(use.Id.ToString()+use.Name); }//自定义的ModelBinder public class MyModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return GetModel(bindingContext.ValueProvider, bindingContext.ModelType, bindingContext.ModelName); } public object GetModel(IValueProvider valueProvider, Type modelType, string modelName) { ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, modelType); //如果绑定的类型是简单类型 if (!modelMetadata.IsComplexType) { return valueProvider.GetValue(modelName).ConvertTo(modelType); } object model = Activator.CreateInstance(modelType); //如果是复杂类型 //前台表单标签的name属性的值有modelName的前缀 if (valueProvider.ContainsPrefix(modelName)) { foreach (PropertyDescriptor porperty in TypeDescriptor.GetProperties(modelType)) { string strkey = modelName + "." + porperty.Name; if (HttpContext.Current.Request.Form.AllKeys.Contains
此例中实现了对简单类型和复杂类型的绑定,在自定义的ModelBinder中,利用ModelMetadata来判定是否是复杂类型,并且利用反射来对指定类型进行实例化,再利用PorpertyDecript的循环来对类下的属性进行赋值。在自定义的ValueProvider中,在ContainsPrefix方法中实现了对请求中的是否包含指定前缀的判定。
在这个自定义的ModelBinder中对复杂类型的绑定时,只能绑定上述User类那样的类型,但是在实际中还有存在嵌套类型的类,例如:public class User{ public int Id{set;get;} public string Name{set;get;} public AAddress Address{set;get;}}public class AAddress{ public string province{set;get} public string City{set;get;} public string County{set;get;} public string Village{set;get;}} 示例5:利用递归完成对复杂类型的模型绑定。(注:相比于示例4,此处对多处多了修改,已完成对复杂类型绑定的支持)//前台//自定义ModelBinder public class MyModelBinder:IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { return GetModel(bindingContext.ValueProvider, bindingContext.ModelType, bindingContext.ModelName); } public object GetModel(IValueProvider valueProvider, Type modelType, string modelName) { ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, modelType); //如果绑定的类型是简单类型 if (!modelMetadata.IsComplexType) { return valueProvider.GetValue(modelName).ConvertTo(modelType); } object model = Activator.CreateInstance(modelType); //如果是复杂类型 //如果表单中标签的name属性的值包含Action的参数名 if (valueProvider.ContainsPrefix(modelName)) { foreach (PropertyDescriptor properdty in TypeDescriptor.GetProperties(modelType)) { if (!valueProvider.ContainsPrefix(modelName + "." + properdty.Name)) continue; ModelMetadata modelMetadataSon = ModelMetadataProviders.Current.GetMetadataForType(() => null, properdty.PropertyType); if (!modelMetadataSon.IsComplexType) { properdty.SetValue(model, valueProvider.GetValue(modelName + "." + properdty.Name).ConvertTo(properdty.PropertyType)); } else { properdty.SetValue(model, GetModel(valueProvider, properdty.PropertyType, modelName + "." + properdty.Name)); } } } else { foreach (PropertyDescriptor properdty in TypeDescriptor.GetProperties(modelType)) { if (!valueProvider.ContainsPrefix(properdty.Name)) continue; ModelMetadata modelMetadataSon = ModelMetadataProviders.Current.GetMetadataForType(() => null, properdty.PropertyType); if (!modelMetadataSon.IsComplexType) { properdty.SetValue(model, valueProvider.GetValue(properdty.Name).ConvertTo(properdty.PropertyType)); } else { properdty.SetValue(model, GetModel(valueProvider, properdty.PropertyType, properdty.Name)); } } } return model; } }//自定义ValueProvider public class MyValueProvider:IValueProvider { private string[] allKeys; public MyValueProvider(ControllerContext controllerContext) { allKeys = controllerContext.RequestContext.HttpContext.Request.Form.AllKeys; } public bool ContainsPrefix(string prefix) { foreach (string key in allKeys) { if (key.Contains(prefix)) return true; } return false; } public ValueProviderResult GetValue(string key) { string[] arrayResult = HttpContext.Current.Request.Form.GetValues(key); string strResult = HttpContext.Current.Request.Form[key]; ValueProviderResult vpr = new ValueProviderResult(arrayResult, strResult, System.Globalization.CultureInfo.CurrentCulture); return vpr; } }//自定义ValueProviderFactory public class MyValueProviderFactory:ValueProviderFactory { public override IValueProvider GetValueProvider(ControllerContext controllerContext) { return new MyValueProvider(controllerContext); } }Index
此示例中,完成了对复杂类型的模型绑定。流程为:如果表单中标签的name属性的值包含了Action参数的参数名,则对其类型的属性进行遍历,并将Action的参数名和遍历的类的当前属性拼接起来,作为key,通过ValueProvider向表单中获取值;如果表单中标签的name属性的值没有包含Action参数的参数名,则对类的属性进行遍历时,将遍历的类的当前属性名作为key,通过ValueProvider来向表单中获取值。 以上我们大体上完成了对简单类型和复杂类型的模型绑定,本着高内聚低耦合的思想,将上述中对简单类型和复杂类型的绑定重写规划下。
数组模型绑定
在写自定义的数组类型模型绑定之前,先来看看默认情况下对数组的模型绑定是如何用的!
在了解如何用默认的ModelBinder来完成数组类型的模型绑定,下面就来完成自定义ModelBinder来完成数组类型的模型绑定
上例中:值得一说的是上例中的GetZeroBasedIndexes方法,它用来生成数字索引,方法利用了yield关键字,实现了延迟和按照上次进度继续执行的思想。
字典类型模型绑定 利用默认ModelBinder的字典类型绑定字典类型是指实现了IDictionary<key,value>借口的类型。
在对字典类型进行模型绑定时,首先要判断绑定的类型是否是字典类型,如何来判断呢?如上图所示,对于字典类的模型绑定,需要判断两个条件:第一,是否为泛型;第二,判断构造当前泛型的泛型类型是否为IDictionary<,>,如果不是的话,再查找该类型是否继承IDictionary<,>接口(即:获取该泛型所继承的所有接口和类,再在这些类和接口中查找是否含有IDictionary<,>)。
集合类型模型绑定 这里的集合指的是除了字典类型和数组类型意外,所有实现了IEnumerable借口的类型 利用默认的ModelBinder来对集合类型进行模型绑定!
可以发现,利用默认的ModelBinder对集合类型的模型绑定和数组类型的模型绑定方式是一样的! 在对集合类型进行绑定时,首先要判断是否是泛型,再判断构造当前泛型的泛型类是否为IList<>或ICollection<>或IEnumerable<>
注意:对集合类型的绑定的判断要放在数组和字典类之后,因为数组也是属于集合类,字典类型也继承自ICollection<T>(如下图)。所以,要在判断集合类之前,先判断是否为数组或字典类型。自定义集合类型的模型绑定
以上就是个人整理的所有有关自定义的模型绑定的所有知识点,在最后对上述绑定中用到的几个重要的方法,来详细介绍下:
一、
private static IEnumerable<string> GetZeroBasedIndexes() { int iteratorVariable0 = 0; while (true) { yield return iteratorVariable0.ToString(); iteratorVariable0++; } } GetZeroBasedIndexes()方法,目的就是提供源源不断的自增1数字,并转换成字符串类型(因为模型绑定时,要对此产生的数据进行字符串拼接,所有直接转换成了string类型)。调用时的格式为:IEnumberable<string> indexes=GetZeroBaseIndexes();此时的indexes中什么都么有,在对indexes进行迭代时,才实时的执行GetZeroBasedIndexes方法,并且从上次执行的位置开始,继续执行。有关yield详细:阿 对于此方法的执行过程,可以自己断电测试下,就可以明白:
二、
public Type ExtractGenericInterface(Type queryType, Type interfaceType)
{ Func<Type, bool> predicate = t => t.IsGenericType && (t.GetGenericTypeDefinition() == interfaceType); if (predicate(queryType)) { return queryType; } else { return queryType.GetInterfaces().FirstOrDefault<Type>(predicate); } }ExtractGenericInterface方法用来判定类型是否为泛型,并且实现了指定的借口。对于方法内部包含的知识点有:1、FunC<T,TResult>,用来封装一个具有一个参数类型为T的参数并返回TResult类型的一个方法,该方法的参数参数为t,==>符号后面的就是内部逻辑。predicate(queryType),实质上就是执行FunC<,>封装的方法,参数为queryTyep;2、t.IsGenericType用来判断t类型是否为泛型;3、t.GetGenericTypeDefinition()方法用来获取构造泛型t的泛型类型(例:如果t为IList<string>,那么t的该方法就是IList<>);3、queryType.GetInterfaces()用来得到queryType类型继承的所有的类和实现的所有方法,返回值的类型为Type[]类型。4、.FirstOrDefault<Type>(predicate)则是用来在Type[]数组中获取第一个满足封装的方法perdicate条件的一个类型!
三、
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) { #region 将当前泛型方法定义的类型参数替换为类型数组的元素,并返回表示结果构造方法的MethodInfo对象 即将当前泛型方法ReplaceCollectionImpl<T>中的T替换为collectionType,之后再去执行方法。状态变化:ReplaceCollectionImpl<T> --> ReplaceCollectionImpl<User> //MethodInfo m = replaceCollectionMethod.MakeGenericMethod(new Type[] { collectionType }); //m.Invoke(null, new object[] { collection, newContents });//执行方法 #endregion 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); } } } }此内部静态类的目的是将list中的数据,转换到指定集合中。由于replaceCollectionMethod是静态的变量,即当该类被加载时就通过反射得到一个MethodInfo类型的实例,之后的MakeGenericMethod方法做的是:将当前泛型方法定义的类型参数替换为类型数组的元素,并返回表示结果构造方法的MethodInfo对象,通俗的讲就是replaceCollectionMethod只是得到了方法,而通过MakeGenericMethod将泛型的参数再加入到通过反射得到的方法中,即状态变化为:ReplaceCollectionImpl<T> -->ReplaceCollectionImpl<collectionType>;之后再通过Invoke来激发通过反射要执行的方法(ReplaceCollectionImpl);在ReplaceCollectionImpl<T>方法内foreach循环便是将list类型向指定的集合类型转化的操作,其中default(T)是根据类型得到默认的值(例:int类型default(T)就是0,string类型default(T)就是null。即:根据类型得到默认值)
以上就是本篇博客总结的模型绑定的全部,其中只对表单提交的值进行了操作!!
尼玛一不小心给搞成日记了,又重新贴了出来!!!
本文转自武沛齐博客园博客,原文链接:http://www.cnblogs.com/wupeiqi/p/3377854.html,如需转载请自行联系原作者