Agenda 
前言 IValueProvider物件透過一個ValueProviderFactory工廠來產生
Action方法綁定Model参数由實現IModelBinder的介面ModelBinder(DefaultModelBinder)物件來實現
在IModelBinder介面中有一個重要的方法object BindModel取得Model參數資料.
但在Http請求傳送參數極為複雜是如何將參數動態綁定在Action參數上呢?
最常見的Json 參數透過POST Body傳到AP端,經由MVC  BindModel來取得參數物件資料.
如下方資料.
1 2 3 4 5 {    "Key" : "123" ,     "value" : "" ,     "Adress" : [ "test133" , "e2424" ]  } 
 
1 2 3 4 5 6 public  class  RootObject {     public  string  Key { get ; set ; }     public  string  value  { get ; set ; }     public  List<string > Adress { get ; set ; } } 
 
網路上有個工具可方便使用Json字串取得c# 對應ModelJson to c# model 
 
本篇就和大家分享這個機制是如何達成的
我有做一個可以針對於Asp.net MVC Debugger 的專案,只要下中斷點就可輕易進入Asp.net MVC原始碼.
 
Model參數類型可能是一個簡單字串或者是一個值類型,也可能是一個複雜類型物件。
對於一個複雜類型物件,基於類型本身和物件成員元數據通過一個ModelMetadata類別來達成
某個成員又可能是一個複雜類型物件,通過ModelMetadata物件表示Model狀態,所以ModelMetadata(元數據)實際上具有一個樹形層次化的資料結構.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public  class  ModelMetadata { 	public  Type ModelType { get ; } 	public  virtual  bool  IsComplexType { get ; } 	public  bool  IsNullableValueType { get ; } 	public  Type ContainerType { get ; } 	public  object  Model { get ; set ; } 	public  string  PropertyName { get ; } 	public  virtual  Dictionary<string , object > AdditionalValues { get ; } 	protected  ModelMetadataProvider Provider { get ; set ; } 	public  virtual  IEnumerable<ModelMetadata> Properties 	{ 		get  		{ 			if  (_properties == null ) 			{ 				IEnumerable<ModelMetadata> originalProperties = Provider.GetMetadataForProperties(Model, RealModelType); 				_propertiesInternal = SortProperties(originalProperties.AsArray()); 				_properties = new  ReadOnlyCollection<ModelMetadata>(_propertiesInternal); 			} 			return  _properties; 		} 	} } 
 
在ModelMetadata類別中有幾個重要的屬性.
Provider(ModelMetadataProvider):存放當前物件下面一個ModelMetadataProvider資訊,ModelMetadataProvider主要是提供ModelMetadata是如何被產生(一般使用CachedDataAnnotationsModelMetadataProvider這個類別使用MemoryCache存放資訊) 
IEnumerable<ModelMetadata>:用來表示當前物件所使用屬性資訊ModelMetadata集合 
IsComplexType:判斷是否是複雜模型. 
ContainerType:父節點類別型態(可以看做樹狀結構,可當作存放根結點類型) 
ModelType:目前屬性或參數的類型. 
Model:綁定完使用的參數 
 
假如這邊有兩個類別Person,AddressInfo且一個Person可以擁有多個地址
這裡就會呈現一對多關係如下圖
就像大樹支點和葉子,這個屬性可能是葉子也可能是別人的支點.
 
1 2 3 4 5 6 7 8 9 10 public  class  Person {     public  int  Age{ get ; set ; }     public  string  Name { get ; set ; }     public  IEnumerable<AddressInfo> Address { get ; set ; } } public  class  AddressInfo {     public  string  Name { get ; set ; } } 
 
上面類別關係圖就是簡單表示複雜模型
通過上面的介紹我們知道表示Model元數據ModelMetadata具有一個樹形層次結構
在每個ModelMetadata內部都有一個型別為IEnumerable<ModelMetadata>的Properties屬性來引用它的下級ModelMetadata,這就形成了一個無限巢狀的後設資料表示結構.
此圖可以表示ModelMetadata跟Person類別屬性的關係.
BindSimpleModel 簡單模型綁定 在上面介紹了ModelMetadata這個類別儲存了參數的各個資訊.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 internal  object  BindSimpleModel (		ControllerContext controllerContext,  		ModelBindingContext bindingContext,  		ValueProviderResult valueProviderResult 	 ){ 	bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); 	if  (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) 	{ 		return  valueProviderResult.RawValue; 	} 	if  (bindingContext.ModelType != typeof (string )) 	{ 		if  (bindingContext.ModelType.IsArray) 		{ 			object  modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); 			return  modelArray; 		} 		Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof (IEnumerable<>)); 		if  (enumerableType != null ) 		{ 			object  modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType); 			Type elementType = enumerableType.GetGenericArguments()[0 ]; 			Type arrayType = elementType.MakeArrayType(); 			object  modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType); 			Type collectionType = typeof (ICollection<>).MakeGenericType(elementType); 			if  (collectionType.IsInstanceOfType(modelCollection)) 			{ 				CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray); 			} 			return  modelCollection; 		} 	} 	object  model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType); 	return  model; } private  static  object  ConvertProviderResult (	ModelStateDictionary modelState,  	string  modelStateKey,  	ValueProviderResult valueProviderResult,  	Type destinationType 	 ){ 	try  	{ 		object  convertedValue = valueProviderResult.ConvertTo(destinationType); 		return  convertedValue; 	} 	catch  (Exception ex) 	{ 		modelState.AddModelError(modelStateKey, ex); 		return  null ; 	} } 
 
透過ConvertProviderResult來將類型轉換成簡單模型綁定使用的參數實例.
在BindSimpleModel中依照下面幾個規則來做參數物件建立.
Array:如果此參數是陣列,判斷此陣列型別並利用ValueProviderResult.ConvertTo()建立陣列 
IEnumerable<>:如果此參數是IEnumerable<>集合,判斷此IEnumerable<>型別ValueProviderResult.ConvertTo()建立集合 
object:不是上面的型別就直接使用ValueProviderResult.ConvertTo()建立物件. 
 
ConvertTo()方法在簡單模型物件建立起到一個很大的作用
 
BindComplexModel 複雜模型綁定 在BindModel方法中有一個BindComplexModel方法是針對複雜模型產生的方法.
一開始先判斷ModelBindingContext.Model是否為Null如果是就會建立一個物件實例返回.
會依照下面機制判斷產生物件
判斷參數類型是否Array產生一個相對應陣列集合 
判斷參數類型是否IDictionary<,> and ICollection<>集合產生一個相對應陣列集合 
判斷參數類型是否IEnumerable<>集合產生一個相對應陣列集合 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 internal  object  BindComplexModel (    ControllerContext controllerContext,      ModelBindingContext bindingContext      ){ 	object  model = bindingContext.Model; 	Type modelType = bindingContext.ModelType; 	if  (model == null  && modelType.IsArray) 	{ 		Type elementType = modelType.GetElementType(); 		Type listType = typeof (List<>).MakeGenericType(elementType); 		object  collection = CreateModel(controllerContext, bindingContext, listType); 		ModelBindingContext arrayBindingContext = new  ModelBindingContext() 		{ 			ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => collection, listType), 			ModelName = bindingContext.ModelName, 			ModelState = bindingContext.ModelState, 			PropertyFilter = bindingContext.PropertyFilter, 			ValueProvider = bindingContext.ValueProvider 		}; 		IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType); 		if  (list == null ) 		{ 			return  null ; 		} 		Array array = Array.CreateInstance(elementType, list.Count); 		list.CopyTo(array, 0 ); 		return  array; 	} 	if  (model == null ) 	{ 		model = CreateModel(controllerContext, bindingContext, modelType); 	} 	Type dictionaryType = TypeHelpers.ExtractGenericInterface(modelType, typeof (IDictionary<,>)); 	if  (dictionaryType != null ) 	{ 		Type[] genericArguments = dictionaryType.GetGenericArguments(); 		Type keyType = genericArguments[0 ]; 		Type valueType = genericArguments[1 ]; 		ModelBindingContext dictionaryBindingContext = new  ModelBindingContext() 		{ 			ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), 			ModelName = bindingContext.ModelName, 			ModelState = bindingContext.ModelState, 			PropertyFilter = bindingContext.PropertyFilter, 			ValueProvider = bindingContext.ValueProvider 		}; 		object  dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType); 		return  dictionary; 	} 	Type enumerableType = TypeHelpers.ExtractGenericInterface(modelType, typeof (IEnumerable<>)); 	if  (enumerableType != null ) 	{ 		Type elementType = enumerableType.GetGenericArguments()[0 ]; 		Type collectionType = typeof (ICollection<>).MakeGenericType(elementType); 		if  (collectionType.IsInstanceOfType(model)) 		{ 			ModelBindingContext collectionBindingContext = new  ModelBindingContext() 			{ 				ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, modelType), 				ModelName = bindingContext.ModelName, 				ModelState = bindingContext.ModelState, 				PropertyFilter = bindingContext.PropertyFilter, 				ValueProvider = bindingContext.ValueProvider 			}; 			object  collection = UpdateCollection(controllerContext, collectionBindingContext, elementType); 			return  collection; 		} 	} 	BindComplexElementalModel(controllerContext, bindingContext, model); 	return  model; } 
 
最後呼叫BindComplexElementalModel方法將剛剛建立值(model物件)透過ValueProvider把參數給值.
有分簡單綁定和複雜綁定,最後都還是會呼叫使用簡單綁定來值綁定給物件.
 
在BindProperty方法時填充子節點ModelMetadata的Model屬性.
GetPropertyValue透過(DefaultModelBinder)再次綁定物件動作如下
ModelMetadata是簡單模型就會把值填充給此次ModelMetadata.Model 
ModelMetadata是複雜模型就建立一個物件後呼叫BindProperty直到找到最後的簡單模型. 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 protected  virtual  void  BindProperty (ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor ){ 	 	IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType); 	object  originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model); 	ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; 	propertyMetadata.Model = originalPropertyValue; 	ModelBindingContext innerBindingContext = new  ModelBindingContext() 	{ 		ModelMetadata = propertyMetadata, 		ModelName = fullPropertyKey, 		ModelState = bindingContext.ModelState, 		ValueProvider = bindingContext.ValueProvider 	}; 	object  newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder); 	propertyMetadata.Model = newPropertyValue; 	 } protected  virtual  object  GetPropertyValue (ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder ){ 	object  value  = propertyBinder.BindModel(controllerContext, bindingContext); 	if  (bindingContext.ModelMetadata.ConvertEmptyStringToNull && Equals(value , String.Empty)) 	{ 		return  null ; 	} 	return  value ; } 
 
小結: MVC  ModelBinding 使用到一個設計模式(組合模式),當我發現時覺得十分興奮.
因為在現實專案中我較少看到(組合模式),發輝良好的作用而在這個案例上發揮的淋漓盡致.
組合模式關係圖如下
參考圖片連結 
組合模式基本上分為兩個部分葉 (Left)和組件 (component)他們都依賴於一個抽象,組件實現取得動作的抽象只為了獲得下面的葉,真正有動作只會在葉有動作
組合模式很適合用在樹狀的資料結構且需求對於葉 和組件 要做大量不一樣判斷.
在模型綁定中他依靠兩個東西完成上面說的依賴關聯
ModelBindingContext物件 
object CreateModel方法 
 
簡單模型綁定 vs 複雜模型綁定 
簡單模型綁定:透過ModelBindingContext找到參數使用型別並利用ValueProvider給值,最後返回物件 
複雜模型綁定:透過ModelBindingContext建立參數利用ValueProvider給值,往下繼續重複動作直到呼叫簡單模型綁定方法,就不會繼續往下呼叫object方法. 
 
這裡很巧妙的利用ModelBinderDictionary取得當前參數型別並取得相對應IModelBinder實現物件.
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/ithelp-day19/   __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW  許可協議。轉載請註明出處!