今天跟大家分享下在Asp.NET Web API中Controller是如何解析從客戶端傳遞過來的數據,然後賦值給Controller的參數的,也就是參數綁定和模型綁定。 Web API參數綁定就是簡單類型的綁定,比如:string,char,bool,int,uint,byte,sbyte,sho
今天跟大家分享下在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; } }View Code
配置路由:
config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/norestful/{controller}/{action}/{id}", defaults: new {id = RouteParameter.Optional} );View Code
WebAPI Controller如下:
public class ValuesController : ApiController { [HttpGet, HttpPost] public int SubNumber(int a,int b) { return a - b; } }View Code
客戶端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); }View Code
輸出結果如下:
如果換成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); }View Code
此方法能解決問題,但是和模型綁定無關,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); }View Code
方法三:利用【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如下圖所示:
好了,今天就到這裡吧。