白話學習MVC(六)模型綁定

1、什麼是模型綁定?

  模型綁定存在的意義就是爲Action的參數提供值,例如:以下表單中提交了數據,那麼Action(即:Index)的參數Id,Name的值就是表單中對應的name屬性相同的值,而表單提交的值是如何賦值給Action的參數的呢?模型綁定就是來完成從用戶提交的請求中提取數據,並賦值給Action的參數。此例是從表單中的提取數據,並賦值給Action的參數,模型綁定還能夠完成完成從地址Url、路由Route、上傳文件等中獲取數據,並賦值給Action相應的參數。html

<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);
        }

2、模型綁定機制介紹

  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);
    }

簡單類型和複雜類型的模型綁定ide

  示例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接口
post

    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.才返回truespa

  示例3:利用自定義ValueProvider和自定義ModelBinder實現模型綁定3d

  • 利用自定義的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方法去獲取表單中的值。那麼下面就來實現這個方法,並在程序中利用!code

  示例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
MyModelBinder

數組模型綁定

  在寫自定義的數組類型模型綁定以前,先來看看默認狀況下對數組的模型綁定是如何用的!

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());
        }
View Code

  在瞭解如何用默認的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++;
            }
        }
 
    }
View Code

  上例中:值得一說的是上例中的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
默認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
自定義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
View Code

  能夠發現,利用默認的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");
        }
View Code

 

 

 

 以上就是我的整理的全部有關自定義的模型綁定的全部知識點,在最後對上述綁定中用到的幾個重要的方法,來詳細介紹下:

 1、 

     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;
            }
        }

 2、

        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方法用來斷定類型是否爲泛型,而且實現了指定的藉口。對於方法內部包含的知識點有:一、FunC<T,TResult>,用來封裝一個具備一個參數類型爲T的參數並返回TResult類型的一個方法,該方法的參數參數爲t,==>符號後面的就是內部邏輯。predicate(queryType),實質上就是執行FunC<,>封裝的方法,參數爲queryTyep;二、t.IsGenericType用來判斷t類型是否爲泛型;三、t.GetGenericTypeDefinition()方法用來獲取構造泛型t的泛型類型(例:若是t爲IList<string>,那麼t的該方法就是IList<>);三、queryType.GetInterfaces()用來獲得queryType類型繼承的全部的類和實現的全部方法,返回值的類型爲Type[]類型。四、.FirstOrDefault<Type>(predicate)則是用來在Type[]數組中獲取第一個知足封裝的方法perdicate條件的一個類型!

3、

    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。即:根據類型獲得默認值)

   

 以上就是本篇博客總結的模型綁定的所有,其中只對表單提交的值進行了操做!!

尼瑪一不當心給搞成日記了,又從新貼了出來!!!

相關文章
相關標籤/搜索