導航
本文主要來講解以下内容:
當Web API在一個控制器中調用一個方法的時候,它必須為參數設定值,這個過程就叫做綁定。這篇文章描述Web API如何綁定參數,以及如何自定義綁定過程。
預設情況,Web API使用如下規則來綁定參數:
1、如果參數一個"簡單"類型,那麼Web API試圖從URI中擷取值。簡單的類型包含.NET的基元類型(int,bool,double等等)加上TimeSpan, DateTime, Guid, decimal, and string,再加上任何的能從字元串進行轉換的類型。
例如,這是一個典型的Web API控制器方法:
這個“id”參數是一個“簡單”類型,是以Web API試圖從請求的URI中擷取參數值,這個“item”參數是一個複雜類型,是以Web API試圖使用一個媒體格式化器從請求消息體中來讀取參數值。
為了更好的讓Web API從URI中讀取複雜類型,添加【FormUri】屬性到參數上。下面的例子定義了一個<code>GeoPoint</code> 的類型,緊接着一個控制器方法從URI中獲得這個GetPoint參數。
這個用戶端可以把Latitude和Longitude的值放進查詢字元串中。Web API将用這兩個參數來構造一個GeoPoint參數。例如:
為了更好的讓Web API 從消息體中讀取一個簡單類型。添加【FromBody】屬性到參數上:
在這個例子中,Web API将使用媒體格式化器來讀取消息體中的name值。這是一個用戶端請求的例子:
當一個參數擁有【FromBody】屬性的時候,Web API使用Content-Type header去選擇一個格式化器。在這個例子中Content-Type是“application/json”,這個請求體是一個原始的Json字元串(而不是Json對象)。
至多一個參數被允許從消息體中讀取值。是以如下這段将不會起作用:
對于這個規則的原因就是這個請求體被存儲在隻能被讀取一次的非緩沖流中。
你也可以讓Web API對待一個class像一個簡單的類型,通過建立一個TypeConverter 并提供一個字元串的轉換。
接下來的代碼展示了用一個GeoPoint類來表示一個地理位置。添加一個 TypeConverter來把字元串轉換為GeoPoint執行個體。這個GeoPoint類用了一個TypeConverter屬性來修飾,并且指定了這個TypeConverter的類型。
現在Web API可以把GeoPoint看做是一個簡單類型。意味着它将可以從URI中綁定GeoPoint參數。在參數上你不需要添加【FromUri】屬性。
用戶端可以調用這個方法,例如如下的URI:
比一個type converter更靈活的選項是建立一個自定義的模型綁定。有了模型綁定,你可以使用像HTTP請求,Action描述,以及路由資料中的原始值。
為了建立一個Model Binder,你需要實作IModelBinder 接口,這個接口中定義了一個方法,BindModel:
接下來為GeoPoint對象來建立一個Model Binder。
一個model binder從一個value provider中獲得原始的錄入值。這個設計分為兩個獨立的方法:
1、這個value provider接收到一個HTTP請求,并且填充一個鍵值對的字典。
2、然後model binder使用鍵值對的字典來填充model。
Web API中預設的value provider從路由資料和查詢字元串中擷取值。例如,這樣一個URI:
value provider将會建立如下的鍵值對:
我們假設使用的是預設的路由模版。
被綁定的參數的名稱被存儲在ModelBindingContext.ModelName這個屬性上。model binder在字典中尋找一個鍵的值。如果這個值存在,并且也能被轉換成GeoPoint,這個model binder将配置設定這個值到ModelBindingContext.Model屬性。
注意:Model Binder不會限制一個簡單類型的轉換,這個model binder首先會在已知位置的清單中尋找,如果查找失敗,将會使用 type converter。
Setting the Model Binder
這裡有幾種方法去設定Model Binder.首先你可以添加一個【Model Binder】屬性到參數上。
你也能添加【Model Binder】屬性到這個參數類型上。Web API将指定這個model binder到這個類型的所有參數上。

接下來的代碼展示如何啟用他們:
有了一個model-binding provider,你仍然需要添加一個[ModelBinder] 屬性到參數上,它的目的就是告知Web API應該是用model binder,而不是使用媒體格式化器。但是現在你不需要在屬性上指定這個model binder的類型。
我前面提到過一個model binder是從value provider中擷取值。寫一個自定義的value provider,實作這個IValueProvider 接口。這個例子是從請求的cookie中擷取值。
你也需要建立一個value provider 工廠通過繼承自ValueProviderFactory 。
添加value provider 工廠到HttpConfiguration ,代碼如下:
Web API組合了所有的value provider,是以當一個model binder調用ValueProvider.GetValue,這個model binder接收從第一個value provider能提供它的值。
或者,通過使用ValueProvider屬性你也能在參數級别上設定value provider 工廠,代碼如下:
這将告訴Web API模型綁定使用指定的value provider 工廠,不要用任何另外的被注冊的value provider。
模型綁定是一個更加普遍機制的特性執行個體。如果你看到這個 [ModelBinder] 屬性,你将明白它是派生自ParameterBindingAttribute 抽象類。這個類定義了一個單獨的方法,并傳回一個HttpParameterBinding 對象:
HttpParameterBinding 對象負責綁定一個值到一個參數。在[ModelBinder]修飾的情況下,這個屬性傳回一個HttpParameterBinding 的實作,它使用了一個IModelBinder 去展現真實的binding。你也可以實作自己的HttpParameterBinding 。
例如,假定你想從請求的<code>if-match</code> 和 <code>if-none-match</code> 的header中擷取ETags。開始我們将定義一個class來代替ETags 。
我們也來定義一個枚舉指明是否從<code>if-match</code> 和 <code>if-none-match</code> 的header中獲得了ETag 。
這裡是HttpParameterBinding ,擷取該 ETag 從所需的标頭并将其綁定到類型的參數的 ETag:
ExecuteBindingAsync 方法來處理綁定。在此方法中,添加參數值到ActionArgument 字典中并在HttpActionContext中。
如果你的ExecuteBindingAsync 方法讀取請求消息體。重寫這個WillReadBody 屬性去傳回true。這個消息體可能是隻能讀一次的未緩沖的流。是以Web API施行了一個規則至多有一個綁定可以讀取消息體。
應用一個自定義的HttpParameterBinding,你能定義一個派生自ParameterBindingAttribute 的屬性,對于ETagParameterBinding,我們将定義兩個屬性:一個是對于<code>if-match</code> Header的,一個是對于if-none-match Header。都派生自一個抽象的基類。
這是一個控制器方法 使用了<code>[IfNoneMatch]</code> 屬性。
除了ParameterBindingAttribute 之外,對于添加一個自定義的HttpParameterBinding 有另外一個挂鈎。在HttpConfiguration 對象上,ParameterBindingRules 是一個匿名方法類型(HttpParameterDescriptor -> HttpParameterBinding)的集合。例如,你可以添加一個規則:在Get請求方法中任何ETag 參數使用<code>ETagParameterBinding</code> with <code>if-none-match。</code>
這個方法對于綁定不适用的參數應該傳回null。
整個參數綁定的過程被一個可插拔的服務控制,IActionValueBinder。IActionValueBinder 的預設實作将執行以下操作:
1、在參數上檢視ParameterBindingAttribute ,這包括 [FromBody], [FromUri], 和[ModelBinder], 或者是自定義的屬性。
2、否則,檢視一個函數的HttpConfiguration.ParameterBindingRules ,它傳回一個非null的HttpParameterBinding 。
3、否則,使用我之前描述的預設規則。
①、如果參數類型是一個“簡單”的,或者擁有一個type converter,将會從URI進行綁定。它等價于在參數上添加[FromUri]屬性。
②、否則,試圖從消息體中讀取參數,這等價于在參數上添加[FromBody]屬性。
如果你需要,你可以用一個自定義的實作來替代整個IActionValueBinder 。
總結
本文主要來講解參數綁定,但是通過上面也可以看出涉及到的知識點還是蠻多的,但是都是很實用的,例子也比較清晰。但是還是需要在項目中進行應用,才能更好的學習和掌握參數綁定的環節。