白话学习MVC(六)模型绑定
一、什么是模型绑定?
模型绑定存在的意义就是为Action的参数提供值,例如:如下表单中提交了数据,那么Action(即:Index)的参数Id,Name的值就是表单中对应的name属性相同的值,而表单提交的值是如何赋值给Action的参数的呢?模型绑定就是来完成从用户提交的请求中提取数据,并赋值给Action的参数。此例是从表单中的提取数据,并赋值给Action的参数,模型绑定还可以完成完成从地址Url、路由Route、上传文件等中获取数据,并赋值给Action相应的参数。
<form id="form0" action="../Home/Index" method="post"> UserName:<input type="text" name="Id" /> PassWord:<input type="text" name="Name" /> <input type="submit" value="Submit"/> </form> |
[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 <h2>Index</h2> <form id="form0" action="../Home/Index" method="post"> <input type="text" name="Temp" /> <input type="submit" value="Submit"/> </form> //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; } }
此示例利用默认的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,只需要在Action的参数前利用模型绑定的ModelBinder特性添加,否则使用默认的模型绑定DefaultModelBinder
- 利用自定义的ValueProvider,需要新建一个自定义的ValueProviderFactory,在ValueProviderFactory的GetValueProvider方法中,实例化自定的ValueProvider并返回。
//自定义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;}
}
//前台 <form id="form0" action="../Home/Index" method="post"> <input type="text" name="use.Name" /> <input type="text" name="Name" /> <input type="submit" value="Submit"/> </form> //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<string>(strkey)) porperty.SetValue(model, valueProvider.GetValue(strkey).ConvertTo(porperty.PropertyType)); } } //不包含前缀,但是标签的name属性的值等于绑定类的属性的名字 else { foreach (PropertyDescriptor porperty in TypeDescriptor.GetProperties(modelType)) { string strkey = porperty.Name; if (HttpContext.Current.Request.Form.AllKeys.Contains<string>(strkey)) porperty.SetValue(model, valueProvider.GetValue(strkey).ConvertTo(porperty.PropertyType)); } } 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('.')) { continue; } string[] temp = key.Split(new char[] { '.' }); if (temp[0] == 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); } } //Global.asax文件 protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); 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()); }
此例中实现了对简单类型和复杂类型的绑定,在自定义的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,此处对多处多了修改,已完成对复杂类型绑定的支持)
//前台 <h2>Index</h2> <form id="form0" action="../Home/Index" method="post"> <table> <tr> <td><input type="text" name="Id" /> </td> <td><input type="text" name="Name" /></td> <td><input type="text" name="Address.province" /> </td> <td><input type="text" name="Address.City" /> </td> </tr> <tr> <td><input type="text" name="use.Address.City" /> </td> <td><input type="text" name="use.Address.County" /> </td> </tr> </table> <input type="submit" value="Submit"/> </form> //自定义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); } }
此示例中,完成了对复杂类型的模型绑定。流程为:如果表单中标签的name属性的值包含了Action参数的参数名,则对其类型的属性进行遍历,并将Action的参数名和遍历的类的当前属性拼接起来,作为key,通过ValueProvider向表单中获取值;如果表单中标签的name属性的值没有包含Action参数的参数名,则对类的属性进行遍历时,将遍历的类的当前属性名作为key,通过ValueProvider来向表单中获取值。
以上我们大体上完成了对简单类型和复杂类型的模型绑定,本着高内聚低耦合的思想,将上述中对简单类型和复杂类型的绑定重写规划下。
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 GetSampleModel(valueProvider, modelType, modelName); } return GetComplexModel(valueProvider, modelType, modelName); } //绑定简单类型 public object GetSampleModel(IValueProvider valueProvider, Type modelType, string modelName) { if (!valueProvider.ContainsPrefix(modelName)) return null; return valueProvider.GetValue(modelName).ConvertTo(modelType); } //绑定复杂类型 public object GetComplexModel(IValueProvider valueProvider, Type modelType, string modelName) { 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; } } ModelBinder
数组模型绑定
在写自定义的数组类型模型绑定之前,先来看看默认情况下对数组的模型绑定是如何用的!
1、数组类型的模型绑定 <form id="form0" action="../Home/Index" method="post"> <table> <tr> <td><input type="text" name="array" /></td> <td><input type="text" name="array" /></td> </tr> </table> <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(string[] array) { return Content(array.Count().ToString()); } -------也就是,在html的标签的name属性中,如果设置的值相同,则默认的数组模型绑定就会将其认定为是数组 2、基于字符串索引的数组类型模型绑定 //前台 <form id="form0" action="../Home/Index" method="post"> <input name="index" type="hidden" value="first" /> <input name="index" type="hidden" value="second" /> <input name="index" type="hidden" value="third" /> <input name="[first]" type="text" value="foo" /> <input name="[second]" type="text" value="bar" /> <input name="[third]" type="text" value="baz" /> <input type="submit" value="Submit"/> </form> //Action [HttpPost] public ActionResult Index(string[] arra) { return Content(arra.Count().ToString()); } -------上例:隐藏类型的name属性必须是index才能被数组获取 3、基于数字索引的数组类型模型绑定 <form id="form0" action="../Home/Index" method="post"> <input name="[0]" type="text" value="foo" /> <input name="[1]" type="text" value="bar" /> <input name="[2]" type="text" value="baz" /> <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(string[] arra) { return Content(arra.Count().ToString()); } ------上例:索引必须是连续的,如果不是的话,后面的值将无法获取。当然其起始也必须是0 对于以上三例:如果他们的同时存在时,如何绑定呢? <form id="form0" action="../Home/Index" method="post"> a) <input type="text" name="arra" value="1" /> <input type="text" name="arra" value="2" /> b) <input type="hidden" name="index" value="first" /> <input type="hidden" name="index" value="second" /> <input type="hidden" name="index" value="third" /> <input type="text" name="[first]" value="111" /> <input type="text" name="[second]" value="222" /> <input type="text" name="[third]" value="333" /> c) <input type="text" name="[0]" value="11" /> <input type="text" name="[1]" value="22" /> <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(string[] arra) { return Content(arra.Count().ToString()); } 即:绑定数组时,优先获取值位置首先是a样式,次之是b样式,再次之是c样式 4、复杂类型的数组绑定 <form id="form0" action="../Home/Index" method="post"> <input name="[0].Id" type="text" value="1" /> <input name="[0].Name" type="text" value="2" /> <input name="[1].Id" type="text" value="11" /> <input name="[1].Name" type="text" value="22" /> <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(User[] arra) { return Content(arra.Count().ToString()); }
在了解如何用默认的ModelBinder来完成数组类型的模型绑定,下面就来完成自定义ModelBinder来完成数组类型的模型绑定
<form id="form0" action="../Home/Index" method="post"> @* <input type="text" name="arra" value="1" /> <input type="text" name="arra" value="2" />*@ @* <input type="text" name="arra.[0]" value="1" /> <input type="text" name="arra.[1]" value="2" /> <input type="text" name="arra.[2]" value="3" /> <input type="text" name="arra.[3]" value="4" />*@ @* <input type="text" name="[0]" value="11" /> <input type="text" name="[1]" value="22" />*@ @* <input type="hidden" name="index" value="first" /> <input type="hidden" name="index" value="second" /> <input type="hidden" name="index" value="third" /> <input type="text" name="[first]" value="111" /> <input type="text" name="[second]" value="222" /> <input type="text" name="[third]" value="333" />*@ @* <input type="text" name="[0].Id" value="3333" /> <input type="text" name="[0].Name" value="3333" /> <input type="text" name="[1].Id" value="4444" /> <input type="text" name="[1].Name" value="4444" />*@ <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index([ModelBinder(typeof(MyModelBinder))]User[] arra) { return Content(arra.Count().ToString()); } //自定义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 GetSampleModel(valueProvider, modelType, modelName); } //如果绑定的类型是数组类型 if (modelType.IsArray) { return GetArrayModel(valueProvider, modelType, modelName); } //绑定的类型是复杂类型 return GetComplexModel(valueProvider, modelType, modelName); } //数组类型 public object GetArrayModel(IValueProvider valueProvider, Type modelType, string modelName) { List<object> list = new List<object>(); //-----表单中的key包含有Action的 参数名 或 参数名. if (valueProvider.ContainsPrefix(modelName)) { ValueProviderResult objVpr = valueProvider.GetValue(modelName); //根据参数名去表单中获取值:name=arra if (objVpr != null) { IEnumerable enumerable = objVpr.ConvertTo(modelType) as IEnumerable; if (null != enumerable) { foreach (var value in enumerable) { list.Add(value); } } } //参数名后面有.:name=arra.[0],默认情况下不支持 else { IEnumerable<string> indexes1 = GetZeroBasedIndexes(); foreach (var index in indexes1) { string indexPrefix = modelName + ".[" + index + "]"; if (!valueProvider.ContainsPrefix(indexPrefix)) { break; } list.Add(GetModel(valueProvider, modelType.GetElementType(), indexPrefix)); } } } //------表单中不包含参数名 //index样式:name=index type=hidden if (valueProvider.ContainsPrefix("index")) { string[] keys = HttpContext.Current.Request.Form.GetValues("index"); for (int i = 0; i < keys.Count(); i++) { list.Add(GetModel(valueProvider, modelType.GetElementType(), "[" + keys[i] + "]")); } } //从零开始的索引样式。即:name=[0]或name=[0].Id IEnumerable<string> indexes = GetZeroBasedIndexes(); foreach (var index in indexes) { string indexPrefix = "[" + index + "]"; if (!valueProvider.ContainsPrefix(indexPrefix)) { break; } list.Add(GetModel(valueProvider, modelType.GetElementType(), indexPrefix)); } object[] array = (object[])Array.CreateInstance(modelType.GetElementType(), list.Count); list.CopyTo(array); return array; } private static IEnumerable<string> GetZeroBasedIndexes() { int iteratorVariable0 = 0; while (true) { yield return iteratorVariable0.ToString(); iteratorVariable0++; } } }
上例中:值得一说的是上例中的GetZeroBasedIndexes方法,它用来生成数字索引,方法利用了yield关键字,实现了延迟和按照上次进度继续执行的思想。
字典类型模型绑定
利用默认ModelBinder的字典类型绑定
<form id="form0" action="../Home/Index" method="post"> <h4>First Person</h4> <input type="hidden" name="[0].key" value="firstPerson"/> First Name: <input name="[0].value.Id" type="text" value="" /> Last Name: <input name="[0].value.Name" type="text" value="" /> <h4>Second Person</h4> <input type="hidden" name="[1].key" value="secondPerson"/> First Name: <input name="[1].value.Id" type="text" value="" /> Last Name: <input name="[1].value.Name" type="text" value="" /> <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(Dictionary<string,User> d) { return View(); } public class User { public int Id { set; get; } public string Name { set; get; } public string Address { set; get; } } 默认ModelBinder
字典类型是指实现了IDictionary<key,value>借口的类型。
在对字典类型进行模型绑定时,首先要判断绑定的类型是否是字典类型,如何来判断呢?
如上图所示,对于字典类的模型绑定,需要判断两个条件:第一,是否为泛型;第二,判断构造当前泛型的泛型类型是否为IDictionary<,>,如果不是的话,再查找该类型是否继承IDictionary<,>接口(即:获取该泛型所继承的所有接口和类,再在这些类和接口中查找是否含有IDictionary<,>)。
public object GetDictionaryModel(IValueProvider valueProvider, Type dictionaryType, string dictionaryName) { List<KeyValuePair<object, object>> list = new List<KeyValuePair<object, object>>(); Type[] genericArguments = dictionaryType.GetGenericArguments(); Type keyType = genericArguments[0]; Type valueType = genericArguments[1]; IEnumerable<string> indexes = GetZeroBasedIndexes(); foreach (var index in indexes) { string indexPrefix = "[" + index + "]"; if (!valueProvider.ContainsPrefix(indexPrefix)) break; string keyPrefix = indexPrefix + ".Key"; string valuePrefix = indexPrefix + ".Value"; list.Add(new KeyValuePair<object, object>(GetModel(valueProvider, keyType, keyPrefix), GetModel(valueProvider, valueType, valuePrefix))); } if (list.Count == 0) return null; Type type = typeof(Dictionary<,>).MakeGenericType(dictionaryType.GetGenericArguments()); object model = Activator.CreateInstance(type); ReplaceHelper.ReplaceDictionary(keyType, valueType, model, list); return model; } } 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; } } } <form id="form0" action="../Home/Index" method="post"> <h4>First Person</h4> <input type="hidden" name="[0].key" value="firstPerson"/> First Name: @Html.TextBox("[0].value.Id ") Last Name: @Html.TextBox("[0].value.Name") <h4>Second Person</h4> <input type="hidden" name="[1].key" value="secondPerson"/> First Name: @Html.TextBox("[1].value.Id") Last Name: @Html.TextBox("[1].value.Name") <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index(Dictionary<string,User> arra) { return Content("dd"); } 自定义ModelBinder
集合类型模型绑定
这里的集合指的是除了字典类型和数组类型意外,所有实现了IEnumerable借口的类型
利用默认的ModelBinder来对集合类型进行模型绑定!
@* <input type="text" name="arra" value="1" /> <input type="text" name="arra" value="2" />*@ @* <input type="text" name="[0]" value="11" /> <input type="text" name="[1]" value="22" />*@ @* <input type="hidden" name="index" value="first" /> <input type="hidden" name="index" value="second" /> <input type="hidden" name="index" value="third" /> <input type="text" name="[first]" value="111" /> <input type="text" name="[second]" value="222" /> <input type="text" name="[third]" value="333" />*@ @* <input type="text" name="[0].Id" value="3333" /> <input type="text" name="[0].Name" value="3333" /> <input type="text" name="[1].Id" value="4444" /> <input type="text" name="[1].Name" value="4444" />*@ public ActionResult Index(List<string> arra) { return Content(arra.Count().ToString()); } //public ActionResult Index(List<User> arra) //{ // return Content(arra.Count().ToString()); //} 默认的ModelBinder
可以发现,利用默认的ModelBinder对集合类型的模型绑定和数组类型的模型绑定方式是一样的!
在对集合类型进行绑定时,首先要判断是否是泛型,再判断构造当前泛型的泛型类是否为IList<>或ICollection<>或IEnumerable<>
注意:对集合类型的绑定的判断要放在数组和字典类之后,因为数组也是属于集合类,字典类型也继承自ICollection<T>(如下图)。所以,要在判断集合类之前,先判断是否为数组或字典类型。
自定义集合类型的模型绑定
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 GetSampleModel(valueProvider, modelType, modelName); } //如果绑定的类型是数组类型 if (modelType.IsArray) { return GetArrayModel(valueProvider, modelType, modelName); } //由于实现了IDictionary<,>接口的类型,肯定也实现了IEnumerable<>接口,所以应该首先判断字典类型 //如果绑定的类型是字典类型 Type dictionaryType = ExtractGenericInterface(modelType, typeof(IDictionary<,>)); if (null != dictionaryType) { return GetCollectionModel(valueProvider, modelType, modelName); } //如果绑定的类型是集合类型 Type enumerableType = ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (null != enumerableType) { return GetCollectionModel(valueProvider, modelType, modelName); } //绑定的类型是复杂类型 return GetComplexModel(valueProvider, modelType, modelName); } //集合类型 public object GetCollectionModel(IValueProvider valueProvider, Type modelType, string modelName) { List<object> list = new List<object>(); Type elementType = modelType.GetGenericArguments()[0];//获取泛型的元素的类型 Type type = typeof(List<>).MakeGenericType(elementType);//根据元素的类型创建泛型的类型 object model = Activator.CreateInstance(type);//根据泛型类型通过反射创建实例 if (valueProvider.ContainsPrefix(modelName)) { ValueProviderResult vpr = valueProvider.GetValue(modelName); if (vpr != null) { IEnumerable enumerable = vpr.RawValue as IEnumerable; if (null != enumerable) { foreach (var value in enumerable) { list.Add(value); } } } } if (list == null) { //index样式:name=index type=hidden if (valueProvider.ContainsPrefix("index")) { string[] keys = HttpContext.Current.Request.Form.GetValues("index"); for (int i = 0; i < keys.Count(); i++) { list.Add(GetModel(valueProvider, modelType.GetGenericArguments()[0], "[" + keys[i] + "]")); } } } if (list == null) { //从零开始的索引样式。即:name=[0]或name=[0].Id IEnumerable<string> indexes = GetZeroBasedIndexes(); foreach (var index in indexes) { string indexPrefix = "[" + index + "]"; if (!valueProvider.ContainsPrefix(indexPrefix)) { break; } list.Add(GetModel(valueProvider, modelType.GetGenericArguments()[0], indexPrefix)); } } if (list == null) return null; ReplaceHelper.ReplaceCollection(modelType.GetGenericArguments()[0], model, list); return model; } public Type ExtractGenericInterface(Type queryType, Type interfaceType) { #region ////当前类型queryType是否是泛型 //bool b = queryType.IsGenericType; ////返回可以构造当前泛型类型的一个泛型类型,即:由IEnumerable<User>得到 IEnumerable<> //Type tt = queryType.GetGenericTypeDefinition(); //bool ttt = tt == interfaceType ? true : false; //委托,相当与匿名方法。t为方法的参数,==>后面是方法内的逻辑。FunC<Type,bool>的Type表示t的类型,匿名方法的返回值 //Func<Type, bool> predicate = delegate(Type queryType2){return false;}; #endregion Func<Type, bool> predicate = t => t.IsGenericType && (t.GetGenericTypeDefinition() == interfaceType); //如果当前类型是泛型,并且该发行是由interfaceType类型构造的。即:此时的t为queryType if (predicate(queryType)) { return queryType; } else { #region ////获取当前类实现的所有类和接口 //Type[] types = queryType.GetInterfaces(); ////在数组中找,并返回满足 predicate 条件的第一个元素 ////也就是在所有父类或实现的接口中找到是泛型并且构造此泛型的类型是interfaceType类型的第一个元素 ////FirstOrDefault<Type>中Type是后面委托predicate的参数类型 //Type tttt = types.FirstOrDefault<Type>(predicate); #endregion return queryType.GetInterfaces().FirstOrDefault<Type>(predicate); } } 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); } } } } <form id="form0" action="../Home/Index" method="post"> @* <input type="text" name="arra" value="1" /> <input type="text" name="arra" value="2" />*@ @* <input type="text" name="[0]" value="11" /> <input type="text" name="[1]" value="22" />*@ @* <input type="hidden" name="index" value="first" /> <input type="hidden" name="index" value="second" /> <input type="hidden" name="index" value="third" /> <input type="text" name="[first]" value="111" /> <input type="text" name="[second]" value="222" /> <input type="text" name="[third]" value="333" />*@ @* <input type="text" name="[0].Id" value="3333" /> <input type="text" name="[0].Name" value="3333" /> <input type="text" name="[1].Id" value="4444" /> <input type="text" name="[1].Name" value="4444" />*@ <input type="submit" value="Submit"/> </form> [HttpPost] public ActionResult Index([ModelBinder(typeof(MyModelBinder))]IList<string> arra) { return Content("d"); }
以上就是个人整理的所有有关自定义的模型绑定的所有知识点,在最后对上述绑定中用到的几个重要的方法,来详细介绍下:
一、
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详细:yield介绍阿
对于此方法的执行过程,可以自己断电测试下,就可以明白:public ActionResult Index() { int i = 0; IEnumerable<int> indexesInt = getNumber(); foreach (int index in indexesInt) { i = i + index; } return View(); } public IEnumerable<int> getNumber() { int ret = 0; while (true) { yield return ret; ret = ret + 1; } }二、
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。即:根据类型得到默认值)
以上就是本篇博客总结的模型绑定的全部,其中只对表单提交的值进行了操作!!
尼玛一不小心给搞成日记了,又重新贴了出来!!!