今天跟大家分享下在Asp.NET Web API中Controller是如何解析從客戶端傳遞過來的數據,然后賦值給Controller的參數的,也就是參數綁定和模型綁定。
Web API參數綁定就是簡單類型的綁定,比如:string,char,bool,int,uint,byte,sbyte,short,ushort,long, float這些基元類型。模型綁定就是除此之外的復雜類型的綁定。大家都知道在MVC中模型綁定都是通過默認的DefaultModelBinder來綁定的,沒有Get請求和POST請之分。然而在Web API中參數和模型綁定的機制在Get請求和POST請求是不一樣的。
一:參數綁定(簡單類型綁定)
Web API參數綁定時,Action默認是從路由數據(url片段)和querystring中獲取數據的。我們都知道,Get請求一個服務的時候,客戶端是把數據放在URL中發送到服務器端的;而POST請求是把數據放到請求體(Request Body)發送到服務器端的。所以默認情況下在WebAPI中我們只能用GET請求去發送簡單類型的數據到服務器端,然后Action再獲取數據,舉個栗子:
准備模型:

public class Number { public int A { get; set; } public int B { get; set; } public Operation Operation { get; set; } } public class Operation { public string Add{get;set;} public string Sub { get; set; } }
配置路由:

config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/norestful/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional} );
WebAPI Controller如下:

public class ValuesController : ApiController { [HttpGet, HttpPost] public int SubNumber(int a,int b) { return a - b; } }
客戶端ajax調用如下:

function ajaxOp(url,type,data,contentType) { $.ajax({ url: url, type: type, data: data, contentType:contentType success:function(result) { alert(result); } }); } function a() { var data = { a: 1, b: 2 }; ajaxOp('/api/norestful/Values/SubNumber', 'POST',data); }
輸出結果如下:
如果換成POST請求,則找不到匹配的action
出現這種情況的原因主要是因為簡單類型的綁定默認情況下是利用【FromUri】特性來解析數據的,光看名稱就知道它只負責從URL中讀取數據,在后面復雜類型綁定時會講到【FromUri】的用法。
當然你要在POST請求下去綁定簡單類型,也是可以的,有三種辦法可以解決。
方法一:請求的數據以querystring的方式把數據放在URL中,POST空數據。
function a() { var data = { a: 1, b: 2 }; ajaxOp('/api/norestful/Values/SubNumber?'+$.param(data), 'POST'); }
方法二:手動從請求中讀取數據:
POST請求下:

[HttpPost] public async Task<IHttpActionResult> AddNumbers() { if (Request.Content.IsFormData()) { NameValueCollection values = await Request.Content.ReadAsFormDataAsync(); int a, b; int.TryParse(values["a"], out a); int.TryParse(values["b"], out b); return Ok(a - b); } return StatusCode(HttpStatusCode.BadRequest); }
此方法能解決問題,但是和模型綁定無關,ReadAsFormDataAsync是HttpRequestMessage類HttpContent屬性的一個擴展方法,該方法負責解析請求頭中content-type類型為application/x-www-form-urlencoded類型中的數據。
Get請求下:

[HttpGet] public IHttpActionResult SubNumberByGet() { Dictionary<string, string> dic = Request.GetQueryNameValuePairs().ToDictionary(x => x.Key, x => x.Value); int a, b; int.TryParse(dic["a"], out a); int.TryParse(dic["b"], out b); return Ok(a-b); }
方法三:利用【FromBody】特性,此特性將從請求體(Request body)中獲取數據。但是【FromBody】特性只能用於Action中的一個參數,如果這樣寫:
[HttpGet, HttpPost] public int SubNumber([FromBody]int a,[FromBody]int b) { return a - b; }
將會拋出無法將多個參數綁定到請求的異常:
所以在POST請求下綁定一個簡單類型利用【FromBody】特性還是可以的,也是最常用的解決方案。
之所以應用【FromBody】特性綁定多個簡單類型拋出異常的原因就是在Web API框架下,請求體(request body)中的數據會以stream流的方式發送到服務器端,而我們沒辦法在模型綁定系統讀取stream流中數據后再去改變它;而不像在MVC框架下在請求開始之前請求體(RequestBody)中的數據被處理后以鍵值對的方式存儲在內存當中,所以MVC Controller中綁定多個復雜類型是沒有問題的。同樣在Web API框架下默認一個Action也不能同時綁定多個復雜類型,這點后面會講到,同時也會提供同時綁定多個復雜類型的相關解決方案。
在Web API框架下,參數綁定(簡單類型綁定)讀取數據有三種不同的方式:
1:Web API首先檢測參數是否應用了【FromBody】特性,如果有,就從該特性直接從請求體中讀取數據。
2:如果沒有【FromBody】特性,Web API就從綁定規則中讀取數據,綁定規則通過在WebApiConfig文件中設置HttpConfiguration的ParameterBindingRules屬性來實現,比如:config.ParameterBindingRules.Insert(0, typeof(Number), o => o.BindWithAttribute(new FromUriAttribute())),指的就是在項目中Number類型默認都是通過【FromUri】特性來獲取數據,這樣不必顯示提供【FromUri】特性了。
3:如果沒有【FromBody】特性,沒有綁定規則,則通過【FroUri】特性讀取數據,這也是參數綁定的默認行為。
二:模型綁定(復雜類型綁定)
Web API復雜類型綁定時候,Action默認從請求體(request body)中獲取數據,所以默認只能用POST請求去發送復雜類型到服務器端,舉個栗子:
Action如下:
[HttpGet,HttpPost] public int AddNumber(Number number) { return number.A + number.B; }
客戶端ajax如下:
function b() { var data = { a: 1, b: 2}; ajaxOp('/api/norestful/Values/AddNumber', 'POST', data); }
Action在默認POST請求下,只能綁定一個復雜類型,如果綁定多個復雜類型,將會拋出異常,原因前面已經提到過。如果要綁定多個復雜類型,至少有四個辦法可以解決。
方法一:在GET請求下,所有類型應用【FromUri】特性。
Action如下:
[HttpGet, HttpPost] public int OpNumbers([FromUri]Number number,[FromUri] Operation op) { return op.Add ? number.A + number.B : number.A - number.B; }
客戶端ajax如下:
function d() { var data = { a: 1, b: 2, add: true, sub: false } ajaxOp('/api/norestful/Values/OpNumbers', 'GET', data); }
方法二:手動從請求中讀取數據,具體實現方法跟上面簡單類型手動從請求中讀取數據的方法是一樣的,就不多講了。
方法三:在GET請求下利用嵌套復雜類型綁定數據,並應用【FromUri】特性
Action如下:
[HttpGet] public int OpNumbersByNestedClass([FromUri]Number number) { return number.Operation.Add ? number.A + number.B : number.A - number.B; }
客戶端ajax如下:
function b2() { var data = { a: 1, b: 2, add: true, sub: false }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'GET', { 'number.a': data.a, 'number.b': data.b, 'number.operation.add': data.add, 'number.operation.sub': data.sub }); }
方法四:POST請求下:
Action如下:
[HttpGet,HttpPost] public int OpNumbersByNestedClass(Number number) { return number.Operation.Add ? number.A + number.B : number.A - number.B; }
客戶端ajax如下:
function b4() { var data = { a: 1, b: 2, 'operation.add': true, 'operation.sub': false }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', data); }
在POST請求下,復雜類型的屬性必須加類型名稱作為前綴,或者var data={a:1,b:2,operation:{add:true,sub:false}}這樣聲明,Action 參數才能獲取到數據。
其實說了這么多,簡單類型綁定和復雜類型綁定在本質上沒什么太大的區別,真正的區別在於數據綁定是通過GET請求(簡單類型的默認方式,復雜類型通過設置【FromUri】實現)還是POST請求( 復雜類型的默認方式,簡單類型通過設置【Frombody】實現)實現的,說白了就是【FromUri】特性和【FromBody】特性之間的區別。現在就講下這兩個特性內部是如何找到數據的。
【FromUri】特性
應用【FromUri】特性,Web API Action中參數將從URL中解析數據,而數據解析是通過值提供程序工廠創建值提供程序來獲取數據的,對於簡單類型,值提供程序則獲取Action參數名稱和參數值;對於復雜類型,值提供程序則獲取類型屬性名稱和屬性值。
下面是Web API中值提供程序工廠類的代碼:
namespace System.Web.Http.ValueProviders { public abstract class ValueProviderFactory { public abstract IValueProvider GetValueProvider(HttpActionContext context); } }
ValueProviderFactory通過HttpActionContext參數來選擇創建什么樣的提供程序來解析數據。
【FromBody】特性
應用【Frombody】特性,Web API Action中參數將從請求體(Request Body),並且通過媒體類型格式化器獲取和綁定數據,在Web API框架下有4中內置的媒體格式化器,分別是:
1:JsonMediaTypeFormatter,對應的content-type是:application/json, text/json
2:XmlMediaTypeFormatter,對應的content-type是:XmlMediaTypeFormatter
3:FormUrlEncodedMediaTypeFormatter,對應的content-type是:對應的content-type是:application/x-www-form-urlencoded。
4:JQueryMvcFormUrlEncodedFormatter,對應的content-type是:對應的content-type是:application/x-www-form-urlencoded。
在默認情況下POST請求采用JQueryMvcFormUrlEncodedFormatter來解析數據的,JQueryMvcFormUrlEncodedFormatter類通過模型綁定系統利用值提供程序從URL中讀取數據,這里的值提供程序是NameValuePairsValueProvider類,該類實現IValueProvider接口來獲取鍵值對中的數據。
當然,你也可以在客戶端請求時指定請求的content-type類型,這樣Web API會根據客戶端的content-type來選擇不同的媒體類型格式化器。如果客戶端采用application/json格式來傳輸數據,Web API在后台則會采用JsonMediaTypeFormatter來解析數據。舉個栗子,上面最后一個例子更改客戶端ajax請求,Controller不變:
function b4() { var data = { a: 1, b: 2, operation: { add: true, sub: false } }; ajaxOp('/api/norestful/Values/OpNumbersByNestedClass', 'POST', JSON.stringify(data), 'application/json'); }
在下圖我們可以看到數據請求格式。這種以Json數據格式傳遞到Action來處理模型綁定,相信在MVC中無處不在吧。
而默認采用JQueryMvcFormUrlEncodedFormatter,則content-type如下圖所示:
好了,今天就到這里吧。