本篇中會為大家介紹在ASP.NET Web API中ModelBinder的綁定原理以及涉及到的一些對象模型,還有簡單的Model綁定示例,在前面的篇幅中講解了Model中繼資料、ValueProvider的子產品,然後還有本篇的Model綁定的子產品這些會結合到後面篇幅中的ParameterBinder子產品中來使用,也就是說在ASP.NET Web API架構中綁定的方式有兩種實作,都是通過ParameterBinder來對參數進行綁定,而在ParameterBinder中的實作則會有兩種方式,今天就給大家單獨的說明一下Model綁定,把它看成一個單獨的功能子產品就行了。
不瞎扯了,直接進入主題,首先我們來看IModelBinder接口類型的定義,所有的ModelBinder功能子產品都實作了IModelBinder接口,如示例代碼1-1
IModelBinder
示例代碼1-1
1
2
3
4
5
6
7
<code>namespace</code> <code>System.Web.Http.ModelBinding</code>
<code>{</code>
<code> </code><code>public</code> <code>interface</code> <code>IModelBinder</code>
<code> </code><code>{</code>
<code> </code><code>bool</code> <code>BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext);</code>
<code> </code><code>}</code>
<code>}</code>
在代碼1-1中我們可以看到BindModel()方法中定義了兩個參數而且都是上下文類型的參數,第一個上下文參數表示操作上下文,它是在控制器方法被執行之前就被建立并且其中封裝了一些後續操作必要的資訊以及存儲請求、響應和參數綁定的結果值,這個稍後會跟大家講解,它起到一個很重要的作用。
第二個上下文參數是綁定上下文參數,這個容易了解,意思就是對象裡封裝着本次要綁定對象的資訊也就是Model中繼資料、ValueProvider之類的資訊,現在不了解也沒關系慢慢往後看看完就會明白的。
HttpActionContext
代碼1-2
8
9
10
11
12
13
14
15
<code>namespace</code> <code>System.Web.Http.Controllers</code>
<code> </code><code>public</code> <code>class</code> <code>HttpActionContext</code>
<code> </code><code>public</code> <code>HttpActionContext();</code>
<code> </code><code>public</code> <code>HttpActionContext(HttpControllerContext controllerContext, HttpActionDescriptor actionDescriptor);</code>
<code> </code><code>public</code> <code>Dictionary<</code><code>string</code><code>, </code><code>object</code><code>> ActionArguments { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>HttpActionDescriptor ActionDescriptor { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>HttpControllerContext ControllerContext { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>ModelStateDictionary ModelState { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>HttpRequestMessage Request { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>HttpResponseMessage Response { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
代碼1-2就是HttpActionContext類型的定義了,下面簡單的描述一下幾個屬性所表示的含義,ActionArguments屬性中的值是對應着控制器方法的參數清單,其中Key值就是參數名稱,Value值就是參數的實際資料值了,因為Model綁定是一個遞歸的過程在複雜類型的子項綁定完畢後并不會對這個屬性進行指派,而是等這一個參數項全部綁定完成了才會進行指派。
HttpActionDescriptor類型的ActionDescriptor屬性,這個是HttpControllerDescriptor類型之後所見的第二個這種描述類型,後面還會有HttpParameterDescriptor類型,在這裡ActionDescriptor屬性中就是封裝着目前所要請求的控制器方法資訊,類似封裝着方法的中繼資料資訊。
ControllerContext屬性就不用多說了想必大家也都知道它的作用,ModelStateDictionary類型的ModelState屬性則是在Model綁定之後才會對其操作,是把參數綁定驗證後的值存在這個屬性當中。
HttpActionContextExtensions
代碼1-3
<code> </code><code>// 摘要:</code>
<code> </code><code>// 包含 System.Web.Http.Controllers.HttpActionContext 的擴充方法。</code>
<code> </code><code>[EditorBrowsable(EditorBrowsableState.Never)]</code>
<code> </code><code>public</code> <code>static</code> <code>class</code> <code>HttpActionContextExtensions</code>
<code> </code><code>public</code> <code>static</code> <code>bool</code> <code>Bind(</code><code>this</code> <code>HttpActionContext actionContext, ModelBindingContext bindingContext);</code>
<code> </code><code>public</code> <code>static</code> <code>bool</code> <code>Bind(</code><code>this</code> <code>HttpActionContext actionContext, ModelBindingContext bindingContext, IEnumerable<IModelBinder> binders);</code>
<code> </code><code>//……</code>
代碼1-3的所示的是包含HttpActionContext類型的擴充方法類型HttpActionContextExtensions,我們在這之中可以看到兩個Bind()方法,這兩個Bind()也是Model綁定至關重要的地方,因為Model綁定的遞歸就是在這裡實作的,至于怎麼實作的稍後會說。
這裡的第一個Bind()方法其實就是調用第二個Bind()方法來執行的。而第二Bind()方法中IEnumerable<IModelBinder>參數則是從HttpActionContext類型中擷取到目前的HttpControllerContext并且再從其中擷取到目前請求的配置對象HttpConfiguration對象,最後從配置對象中的容器屬性中擷取ModelBinder的提供程式集合,然後根據目前ModelBindingContext中的ModelType類型使用提供程式集合來判斷後擷取适合類型的IModelBinder集合,進而調用第二個Bind()方法。
這樣看可能還是不太了解遞歸的情況,大家稍安勿躁,後面慢慢講解。
ModelBindingContext
代碼1-4
16
17
18
19
20
21
22
<code> </code><code>// 提供運作模型聯程式設計式的上下文。</code>
<code> </code><code>public</code> <code>class</code> <code>ModelBindingContext</code>
<code> </code><code>// 摘要:</code>
<code> </code><code>// 初始化 System.Web.Http.ModelBinding.ModelBindingContext 類的新執行個體。</code>
<code> </code><code>public</code> <code>ModelBindingContext();</code>
<code> </code><code>public</code> <code>ModelBindingContext(ModelBindingContext bindingContext);</code>
<code> </code><code>public</code> <code>bool</code> <code>FallbackToEmptyPrefix { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>object</code> <code>Model { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>ModelMetadata ModelMetadata { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>string</code> <code>ModelName { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>ModelStateDictionary ModelState { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>Type ModelType { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>IDictionary<</code><code>string</code><code>, ModelMetadata> PropertyMetadata { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>ModelValidationNode ValidationNode { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
<code> </code><code>public</code> <code>IValueProvider ValueProvider { </code><code>get</code><code>; </code><code>set</code><code>; }</code>
代碼1-4中所示的就是綁定上下文對象,首先我們看到它的重載構造函數中有個ModelBindingContext類型的參數用以表示嵌套,内部的實作是用以傳遞ModelState屬性的狀态值和ValueProvider值提供程式,至于為什麼是這種結構?這個跟綁定複雜類型的時候有關,構造就如同ModelState屬性對象的ModelStateDictionary類型一樣,這種結構稍後會講解。
當中的Model屬性表示目前ModelBindingContext中綁定過後的Model值,然後ModelMetadata、ModelName、ModelType、PropertyMetadata這些屬性都是表示目前ModelBindingContext中Model的對應值。這個”目前”可能是string類型,也可能是複雜類型。(複雜類型在綁定的時候會被ASP.NET Web API架構封裝起來有個特定的類型,這個稍後講解)
簡單類型綁定器以及綁定器提供程式
簡單類型綁定器- TypeConverterModelBinder
代碼1-5
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<code> </code><code>public</code> <code>sealed</code> <code>class</code> <code>TypeConverterModelBinder : IModelBinder</code>
<code> </code><code>// Methods</code>
<code> </code><code>public</code> <code>bool</code> <code>BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)</code>
<code> </code><code>{</code>
<code> </code><code>object</code> <code>obj2;</code>
<code> </code><code>ModelBindingHelper.ValidateBindingContext(bindingContext);</code>
<code> </code><code>ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);</code>
<code> </code><code>if</code> <code>(result == </code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>return</code> <code>false</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>bindingContext.ModelState.SetModelValue(bindingContext.ModelName, result);</code>
<code> </code><code>try</code>
<code> </code><code>obj2 = result.ConvertTo(bindingContext.ModelType);</code>
<code> </code><code>catch</code> <code>(Exception exception)</code>
<code> </code><code>if</code> <code>(IsFormatException(exception))</code>
<code> </code><code>{</code>
<code> </code><code>string</code> <code>errorMessage = ModelBinderConfig.TypeConversionErrorMessageProvider(actionContext, bindingContext.ModelMetadata, result.AttemptedValue);</code>
<code> </code><code>if</code> <code>(errorMessage != </code><code>null</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>bindingContext.ModelState.AddModelError(bindingContext.ModelName, errorMessage);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>else</code>
<code> </code><code>bindingContext.ModelState.AddModelError(bindingContext.ModelName, exception);</code>
<code> </code><code>ModelBindingHelper.ReplaceEmptyStringWithNull(bindingContext.ModelMetadata, </code><code>ref</code> <code>obj2);</code>
<code> </code><code>bindingContext.Model = obj2;</code>
<code> </code><code>return</code> <code>true</code><code>;</code>
<code> </code><code>}</code>
在代碼1-5中,我們看到TypeConverterModelBinder類型實作了IModelBinder接口,并且在BindModel()方法中直接就是使用綁定上下文中的ValueProvider根據綁定上下文中的ModelName屬性,ModelName就是我們前面ValueProvider篇幅中講解到的字首值和屬性值的合并。而後會将擷取到的結果值進行類型判斷,如果不能正确的轉換成string類型則會提示各種異常,當然了這種異常不會報出來,隻是添加到了目前綁定上下文的ModelState屬性中,如果可以轉換成功則會對目前綁定上下文的Model值進行指派。
簡單類型綁定器提供程式- TypeConverterModelBinderProvider
代碼1-6
<code> </code><code>public</code> <code>sealed</code> <code>class</code> <code>TypeConverterModelBinderProvider : ModelBinderProvider</code>
<code> </code><code>public</code> <code>override</code> <code>IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)</code>
<code> </code><code>if</code> <code>(modelType == </code><code>null</code><code>)</code>
<code> </code><code>throw</code> <code>Error.ArgumentNull(</code><code>"modelType"</code><code>);</code>
<code> </code><code>if</code> <code>(!TypeHelper.HasStringConverter(modelType))</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code> </code><code>return</code> <code>new</code> <code>TypeConverterModelBinder();</code>
代碼1-6中所示TypeConverterModelBinderProvider類型則為簡單類型綁定器的提供程式,并且繼承自ModelBinderProvider類型,講到這裡了我才發現我把這個類型忘記說明了,不過沒關系,大家自行看一下就好了,ModelBinderProvider就是一個抽象類,然後定義了一個抽象的行為。
在TypeConverterModelBinderProvider類型的實作中,我們可以清楚的看到如果參數modelType可以成功的轉換成string類型則會傳回TypeConverterModelBinder類型的執行個體,不然則傳回null。
複雜類型綁定器(封裝器)以及複雜類型綁定器(封裝器)提供程式
複雜類型封裝對象-ComplexModelDto
代碼1-7
<code>namespace</code> <code>System.Web.Http.ModelBinding.Binders</code>
<code> </code><code>// 表示一個複雜模型的資料傳輸對象 (DTO)。</code>
<code> </code><code>public</code> <code>class</code> <code>ComplexModelDto</code>
<code> </code><code>public</code> <code>ComplexModelDto(ModelMetadata modelMetadata, IEnumerable<ModelMetadata> propertyMetadata);</code>
<code> </code><code>public</code> <code>ModelMetadata ModelMetadata { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>Collection<ModelMetadata> PropertyMetadata { </code><code>get</code><code>; }</code>
<code> </code><code>public</code> <code>IDictionary<ModelMetadata, ComplexModelDtoResult> Results { </code><code>get</code><code>; }</code>
大家也看到了代碼1-7中的注釋部分,表示一個複雜模型(Model)的資料傳輸對象,實際就是對複雜類型的重新封裝,封裝的方式大家也看到了都是以Model中繼資料的方式,這個類型我就不多說了。對于Model中繼資料不太清楚的朋友建議去把前面的篇幅看一下。
複雜類型封裝器-MutableObjectModelBinder
對于MutableObjectModelBinder類型中的實作代碼我就不貼了太多了,在這些理論基礎都講完之後後面的篇幅中會有代碼示例的。
這裡我就用文字來描述一下MutableObjectModelBinder類型所要實作的功能,為什麼叫MutableObjectModelBinder為複雜類型封裝器呢?因為MutableObjectModelBinder類型它不幹綁定的事情,在它執行的時候就一定可以判定目前綁定上下文的Model是一個複雜類型,這個時候MutableObjectModelBinder會根據目前綁定上下文中的Metadata下的Properties屬性擷取到目前Model下屬性的Model中繼資料清單,并且根據每一項的Model中繼資料進行篩選,篩選的條件依賴于應用在Model屬性上的特性,也就是HttpBindingBehaviorAttribute類型,對于這個類型看完這些之後自己去琢磨吧。
在擷取到Model下對應屬性的Model中繼資料集合後,然後建立ComplexModelDto對象執行個體,并且建立一個綁定上下文把建立的ComplexModelDto對象執行個體作為Model、ModelType這些相關屬性,然後最後會調用actionContext.Bind(context);,也就是代碼1-3中的HttpActionContext擴充方法進入Model綁定中的遞歸。
複雜類型封裝器提供程式- MutableObjectModelBinderProvider
代碼1-8
<code> </code><code>public</code> <code>sealed</code> <code>class</code> <code>MutableObjectModelBinderProvider : ModelBinderProvider</code>
<code> </code><code>if</code> <code>(!MutableObjectModelBinder.CanBindType(modelType))</code>
<code> </code><code>return</code> <code>new</code> <code>MutableObjectModelBinder();</code>
代碼1-8中可以看到在根據類型判斷的時候是調用的MutableObjectModelBinder中的靜态方法CanBindType(),在CanBindType()方法實作中判斷類型不能為ComplexModelDto類型和string類型的,string類型的好了解,因為是屬于TypeConverterModelBinder類型來綁定的,ComplexModelDto類型是為了防止架構的處理進入一個死循環,這個看到最後大家就會明白的。
複雜類型綁定器- ComplexModelDtoModelBinder
代碼1-9
<code> </code><code>public</code> <code>sealed</code> <code>class</code> <code>ComplexModelDtoModelBinder : IModelBinder</code>
<code> </code><code>ModelBindingHelper.ValidateBindingContext(bindingContext, </code><code>typeof</code><code>(ComplexModelDto), </code><code>false</code><code>);</code>
<code> </code><code>ComplexModelDto model = (ComplexModelDto)bindingContext.Model;</code>
<code> </code><code>foreach</code> <code>(ModelMetadata metadata </code><code>in</code> <code>model.PropertyMetadata)</code>
<code> </code><code>ModelBindingContext context = </code><code>new</code> <code>ModelBindingContext(bindingContext)</code>
<code> </code><code>ModelMetadata = metadata,</code>
<code> </code><code>ModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, metadata.PropertyName)</code>
<code> </code><code>};</code>
<code> </code><code>if</code> <code>(actionContext.Bind(context))</code>
<code> </code><code>model.Results[metadata] = </code><code>new</code> <code>ComplexModelDtoResult(context.Model, context.ValidationNode);</code>
<code> </code><code>}</code>
看這代碼1-9中所示類型的名字不用說也是用來對ComplexModelDto對象進行處理的,可以在代碼實作中看出來,先把綁定上下文中的Model擷取出來轉換成ComplexModelDto執行個體對象,然後周遊其屬性PropertyMetadata,根據其每一項的Model中繼資料建立一個綁定上下文,然後調用actionContext.Bind(context)方法,這也是Model綁定遞歸的過程之一。這種情況會出現在初始Model類型是複雜類型并且其屬性中也有複雜類型。
複雜類型綁定器提供程式- ComplexModelDtoModelBinderProvider
代碼1-10
<code> </code><code>public</code> <code>override</code> <code>IModelBinder GetBinder(HttpConfiguration configuration, Type modelType)</code>
<code> </code><code>if</code> <code>(modelType == </code><code>null</code><code>)</code>
<code> </code><code>throw</code> <code>Error.ArgumentNull(</code><code>"modelType"</code><code>);</code>
<code> </code><code>if</code> <code>(!(modelType == </code><code>this</code><code>.ModelType))</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>.SuppressPrefixCheck)</code>
<code> </code><code>return</code> <code>this</code><code>._modelBinderFactory();</code>
<code> </code><code>return</code> <code>new</code> <code>SimpleModelBinder(</code><code>this</code><code>);</code>
代碼1-10并不是ComplexModelDtoModelBinderProvider類型中本身的實作,而是其本質的實作是SimpleModelBinderProvider類型來完成的,分為檢查Model的字首和不檢查兩種。這個自行看一下就知道了。
我們看下整體的結構圖。
圖1
<a href="http://s3.51cto.com/wyfs02/M02/49/CB/wKiom1QayEnSqZ0HAAFLFlblcFs621.jpg" target="_blank"></a>
當然了還有其他的ModelBinder類型這裡就不一一講解了。最後我們看一下模拟複雜綁定的示意圖。
圖2
<a href="http://s3.51cto.com/wyfs02/M00/49/CD/wKioL1QayHfiIjorAANhslNAj_g965.jpg" target="_blank"></a>
這裡的Product是一個複雜類型,其中有三個string類型的屬性,執行的順序為紅、藍、黃。這裡要說明的就是除了紅色部分進入HttpActionContextExtensions之後不會再次遞歸,其他藍色和***部分均有可能,隻要碰到有複雜類型。
大概的說明一下流程代碼部分在示例篇章會貼出來,首先在Product類型進行綁定的時候會先擷取到Product的類型,然後根據目前架構中注冊的一系列ModelBinder提供程式進行篩選擷取到可以對複雜類型進行綁定的ModelBinder對象,在上圖中也就是MutableObjectModelBinder類型,在MutableObjectModelBinder類型進行中會将Product類型的所有屬性Model中繼資料進行封裝,封裝為ComplexModelDto對象執行個體,然後MutableObjectModelBinder類型會生成一個ModelBindingContext1對象執行個體,調用HttpActionContext類型的擴充方法類型HttpActionContextExtensions中的方法Bind()進行Model綁定,在Bind()方法中會重複紅色線條流程部分,意思就是說會根據ModelBindingContext1對象執行個體中的Metadata屬性擷取到Model類型,剛才我們也說過了Model類型被封裝為ComplexModelDto類型了,而後根據這個類型進行篩選擷取到ComplexModelDtoModelBinderProvider提供程式,随之生成ComplexModelDtoModelBinder執行個體,在ComplexModelDtoModelBinder執行Model綁定的處理過程中,會周遊ComplexModelDto類型執行個體中的每一項屬性中繼資料并且生成對應的ModelBindingContext,在上圖也就是ModelBindingContext2以及在ModelBindingContext2執行綁定操作後的ModelBindingContext3。在ModelBindingContext2生成完畢之後會再次的調用HttpActionContext類型的擴充方法類型HttpActionContextExtensions中的方法Bind()進行Model綁定,因為Product中的屬性都是string類型是以不存在複雜類型,按照上圖中的順序大家可以看出,如果是複雜類型則會重新執行到紅色線條的起始部分。因為這個時候是string類型是以篩選出的提供程式類型為TypeConverterModelBinderProvider,進而生成TypeConverterModelBinder執行個體為之綁定。
本文轉自jinyuan0829 51CTO部落格,原文連結:http://blog.51cto.com/jinyuan/1554989,如需轉載請自行聯系原作者