白话学习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);} //自定義ModelBinderpublic 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并返回。
//自定義ModelBinderpublic class MyModelBinder:IModelBinder{public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext){return bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(bindingContext.ModelType);}}
//自定義ValueProviderpublic 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;}}
//自定義ValueProviderFactorypublic 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); //by default, ivalueprovider有5種類型childactionvalueprovider/formvalueprovider/routedata
provider/querystringprovider/httpfilecollectionvalueproviderValueProviderFactories.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);}//自定義的ModelBinderpublic 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)) //這里modelType是class類型,不是class的Type{string strkey = porperty.Name;if (HttpContext.Current.Request.Form.AllKeys.Contains<string>(strkey))porperty.SetValue(model, valueProvider.GetValue(strkey).ConvertTo(porperty.PropertyType));}}return model;}}//自定義的ValueProviderpublic 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;}}
//自定義的ValueProviderFactorypublic 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>//自定義ModelBinderpublic 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;}}//自定義ValueProviderpublic 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;}}//自定義ValueProviderFactorypublic 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());}//自定義ModelBinderpublic 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=arraif (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=hiddenif (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].IdIEnumerable<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=hiddenif (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].IdIEnumerable<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;};#endregionFunc<Type, bool> predicate = t => t.IsGenericType && (t.GetGenericTypeDefinition() == interfaceType);//如果當前類型是泛型,并且該發行是由interfaceType類型構造的。即:此時的t為queryTypeif (predicate(queryType)){return queryType;}else{#region獲取當前類實現的所有類和接口//Type[] types = queryType.GetInterfaces();在數組中找,并返回滿足 predicate 條件的第一個元素也就是在所有父類或實現的接口中找到是泛型并且構造此泛型的類型是interfaceType類型的第一個元素FirstOrDefault<Type>中Type是后面委托predicate的參數類型 //Type tttt = types.FirstOrDefault<Type>(predicate);#endregionreturn 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 });//執行方法#endregionreplaceCollectionMethod.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介紹阿?
對于此方法的執行過程,可以自己斷電測試下,就可以明白:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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。即:根據類型得到默認值)
?
?以上就是本篇博客總結的模型綁定的全部,其中只對表單提交的值進行了操作!!
尼瑪一不小心給搞成日記了,又重新貼了出來!!!
轉載于:https://www.cnblogs.com/sjqq/p/7531447.html
總結
以上是生活随笔為你收集整理的白话学习MVC(六)模型绑定的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国窖1573多少钱一瓶
- 下一篇: 敏开头成语有哪些啊?