本文將概述在WebAPI方式下將如何將參數綁定到一個action方法,包括參數是如何被讀取,一系列規則決定特定環境采用的那種綁定方式,文章最后將給出一些實際的例子。
Parameter binding說到底是接到一個Http請求,將其轉換成.NET類型使得action方法的簽名更易于理解。
請求消息(request message)包括了請求的所有信息,如帶查詢字符串的請求地址(URL),內容主體(content body)及頭部信息(header)。在沒有采用parameter binding
的情況下,每個action方法將需要接收request message,并手動從中提取出參數,如下所示:
public object MyAction(HttpRequestMessage request) { // make explicit calls to get parameters from the request object int id = int.Parse(request.RequestUri.ParseQueryString().Get("id")); // need error logic! Customer c = request.Content.ReadAsAsync<Customer>().Result; // should be async! // Now use id and customer }
很顯然,這樣的方式丑陋,易出錯,代碼重復,而且難以單元測試。我們希望action的簽名類似以下的形式:
public object MyAction(int id, Customer c) { }
那么WebAPI是如何將request message轉換成像id和customer這樣的參數的呢?
Model Binding vs. Formatters
參數綁定有兩種技術:Model Binding和Formatters。實際上,WebAPI使用model binding讀取查詢字符串(query string)內容進行參數綁定,使用Formatters讀取主體內容
(body content)進行參數的綁定。
- Using Model Binding:
ModelBinding和MVC中的此概念是一致的,更多內容見Here。通常有一個"ValuePeoviders"提供數據片斷如查詢字符串參數,model binder將這些片斷組合成一個對象。
- Using Formatters:
Formatters(如MediaTypeFormatter類所示)實際上是包含額外元數據的序列化程序。WebAPI從HttpConfiguration中獲取一個formatters的列表,然后通過request信息
中的content-type來判斷采用具體合適的formatter。WebAPI有不少默認的formatters。默認的JSON formatter是JSON.NET。還有Xml formatter和采用JQuery語法的
FormUrl formatter。
其中Formatters的核心方法是MediaTypeFormatter.ReadFromStreamAsync,如下所示:
public virtual Task<object> ReadFromStreamAsync(
Type type,
Stream stream,
HttpContentHeaders contentHeaders,
IFormatterLogger formatterLogger)
其中,Type指代參數的類型,將傳遞給序列化器serializer。Stream是請求信息的content stream。Read方法將讀取stream,將其實例化為一個對象,然后返回它。
HttpContentType來自請求信息。IFormatterLogger是一個回調接口,fomatter正是通過此接口來記錄讀取中的錯誤。
model binding和formatter都支持驗證和錯誤信息記錄。然后,相比formatter而言model binding更靈活。
何時采用特定的參數綁定方式?(when do we use which?)
以下這些基本原則決定了parameter是通過modelbinding還是formatter來讀取的:
如果參數未添加任何特性字段[attribute]標明,那么這將由參數的.NET類型來決定具體采用何種方式。簡單類型"Simple types"采用model binding。復雜類型"Complex types"
- 采用formatters。簡單類型包括:primitives,Timespan,DateTime,Guid,Decimal,String,或者通過類型轉換器(TypeConverter)從字符串轉換過來的類型。
- 通過使用[FormBody]特性標示特定參數應該從body中取值。
- 通過使用[ModelBinder]特性來標示參數或參數類型應該使用model bound方式。這個特性也可以用來配置model binder。[FromUri]是一個從[ModelBinder]繼承而來的
實體,用于配置model binder只應用到URL中的數據。 - body只能被讀取一次。如果簽名中有兩個負責類型,至少其中一個必須添加[ModelBinder]特性標注。
以下是使得這些原則得以穩定并可預測的關鍵設計。
(body 只能被讀取一次)Only one thing can read the body
MVC和WebAPI之間的一個關鍵不同點在于MVC緩存請求主體(request body)。這意味著MVC的參數綁定可以反復從body中查找參數片斷。然而,在WebAPI中,請求主體(HttpContent)
只能被讀取一次,不被緩存,只能向前讀取的流。這意味著parameter binding需要謹慎對待stream,除非在需要綁定參數的情況下,否則stream不能被讀取。
以下的action方法想直接讀取stream,因而導致WebAPI不能保證其擁有用于參數綁定的stream。考慮如下的action方法:
// Action saves the request’s content into an Azure blob public Task PostUploadfile(string destinationBlobName) { // string should come from URL, we’ll read content body ourselves. Stream azureStream = OpenAzureStorage(destinationBlobName); // stream to write to azure return this.Request.Content.CopyToStream(azureStream); // upload body contents to azure. }
參數是一個簡單類型,因而這將從查詢字符串中讀取。由于action簽名中并不包含任何 負責類型,WebAPI將永遠不會讀取request content stream,因而這里的action方法可以讀取它。
示例
以下給出一些不同請求的示例說明它們將如何映射到特定action簽名:
/?id=123&name=bob
void Action(int id, string name) // 所有參數都是簡單類型,因而都將來自url
/?id=123&name=bob
void Action([FromUri] int id, [FromUri] string name) // 同上
void Action([FromBody] string name); //[FormBody]特性顯示標明讀取整個body為一個字符串作為參數
public class Customer { // 定義的一個復雜對象類型
public string Name { get; set; }
public int Age { get; set; }
}
/?id=123
void Action(int id, Customer c) // 參數id從query string中讀取,參數c是一個復雜Customer對象類戲,通過formatter從body中讀取
void Action(Customer c1, Customer c2) // 出錯!多個參數都是復雜類型,都試圖從body中讀取,而body只能被讀取一次
void Action([FromUri] Customer c1, Customer c2) // 可以!不同于上面的action,復雜類型c1將從url中讀取,c2將從body中讀取
void Action([ModelBinder(MyCustomBinder)] SomeType c) // 標示使用特定的model binder來解析參數
[ModelBinder(MyCustomBinder)] public class SomeType { } // 通過給特定類型SomeType聲明標注[ModelBidner(MyCustomBinder)]特性使得所有SomeType類型參數應用此規則
void Action(SomeType c) // 由于c的類型為SomeType,因而應用SomeType上的特性決定其采用model binding
與MVC的區別
以下是MVC和WebAPI在參數綁定上的一些不同點:
- MVC只具有model binding,而沒有formatters。這是由于MVC將對request body也應用model bind 進行解析,而WebAPI對request body將使用serializer來解析。
- MVC將緩存request body,因而能夠很容易的將其傳遞給model binding。WebAPI不緩存request body,因而默認將不對request body應用model binding解析。
- WebAPI的綁定完全可以通過action簽名的類型來決定。比如:在WebAPI中,我們知道一個參數最終將從body還是query string中讀取綁定。然而,在MVC中,model binding
系統將同時查找body和query string數據進行解析綁定。
文章列表