基於.Net Framework 4.0 Web API開發(2):ASP.NET Web APIs 引數傳遞方式詳解

行與止發表於2016-07-11

概述: 

  ASP.NET Web API 的好用使用過的都知道,沒有複雜的配置檔案,一個簡單的ApiController加上需要的Action就能工作。呼叫API過程中引數的傳遞是必須的,本節就來談談API使用過程中引數的傳遞方式。

各種引數傳遞方式的實現:

      ASP.NET Web API引數有兩種傳遞方式,一種是請求時攜帶QueryString,Action中沒有表中標註FromUri和FromBody屬性且沒有預設值的引數,Request請求時必需一QueryString的方式攜帶引數?A=&B=&C=1,及時沒有值,也得傳遞,否則報404錯誤。

  API 開發中的FromUriAttribute屬性,主要是用來在GET請求中傳遞複雜物件,並且每個API可以含有多個此型別的引數,但每個複雜物件中的屬性名不能相同,否則無法正確傳值,除非兩個引數的相同屬性的屬性值相同,不過此種方式傳遞有一定的侷限性,就是url長度限制,變相的限定此種方式的引數資料量的大小。

     另外一種傳遞方式就是請求Request的傳送內容攜帶資料,對應API開發中的FromBodyAttribute屬性,此種方式主要應對POST請求引數的傳遞,可以傳遞複雜的型別,包括陣列,list等,但是每個API有且僅有一個這樣的引數,如果有多個,就會報無法將多個引數繫結到請求的內容

  不多上,下面上程式碼,一種一種講解:

 1. 簡單型別傳遞

  簡單型別包括 int(decimal,long,float)、string(char)、bool、datetime、guid,主要就這幾種

     模板: public  TResult nameOfFunction([FromUrl]int i, [FromUrl]string s, ……)

         => public  TResult nameOfFunction(int i, string s, ……)

   但模板: public  TResult nameOfFunction([FromUrl]ClassA a, [FromUrl]Class b, ……)

         ≠> public  TResult nameOfFunction(ClassA a, ClassB b, ……)

 1         [HttpGet]
 2         public ResultData TestParameter(int i, string s, bool b, DateTime t, Guid g)
 3         {
 4             try
 5             {
 6                 var data = new { i = i, s = s, b = b, t = t, g = g};
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }
15 
16         //TestParameter
17 function TestParameter() {
18     var v = { i: 1, b: false, t: "2016-07-06", g: "E816F0B7-2FB7-47D9-84ED-119F58C9BEC5", s: "test"};
19     $.ajax({
20         type: "get",
21         url: host + "/mobileapi/test/TestParameter",
22         dataType: "text",
23         data: v,
24         success: function (data) {
25             alert(data);
26         },
27         error: function (x, y, z) {
28             alert("報錯無語");
29         }
30     });
31 }

結果如下:

注意:

 1. GET 型別請求的API不能含有[FromBody]屬性的引數,雖然不會報錯,但永遠沒為null,如果GET請求需要傳遞複雜引數,可以用FromUri屬性修飾引數

 2. API引數沒有預設值的情況下,請求的引數名稱必需與API引數名稱保持一致,但不區分大小寫,且能對應上的引數個數一定相等,否則會報404錯誤。

詳細列舉如下:

     a. 引數個數相等,但對應的引數少個 g,所有找不到對應API,報404錯誤

 1 function TestParameter() {
 2     var v = { i: 1, b: false, t: "2016-07-06", s: "test", a: "會報404錯誤" };
 3     $.ajax({
 4         type: "get",
 5         url: host + "/mobileapi/test/TestParameter",
 6         dataType: "text",
 7         data: v,
 8         success: function (data) {
 9             alert(data);
10         },
11         error: function (x, y, z) {
12             alert("報錯無語");
13         }
14     });
15 }

     b. 引數個數不相等,但是能對應上的引數個數相等,不會報404錯誤

 1 //TestParameter
 2 function TestParameter() {
 3     var v = { i: 1, b: false, t: "2016-07-06", s: "test", g: "E816F0B7-2FB7-47D9-84ED-119F58C9BEC5", a: "不會報404錯誤" };
 4     $.ajax({
 5         type: "get",
 6         url: host + "/mobileapi/test/TestParameter",
 7         dataType: "text",
 8         data: v,
 9         success: function (data) {
10             alert(data);
11         },
12         error: function (x, y, z) {
13             alert("報錯無語");
14         }
15     });
16 }

  如何調整使上述幾種方式也能找到正確的API的呢?這就需要.NET Framework的預設引數功能,API的調整如下:

 1  [HttpGet]
 2         public ResultData TestParameter(int i, string s, bool b, DateTime t, Guid? g = null)
 3         {
 4             try
 5             {
 6                 var data = new { i = i, s = s, b = b, t = t, g = g };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

 以上程式碼最後一個引數g有個預設值null,這樣get請求的時候可以沒有g引數也能請求通過,不會報404錯誤。另外,值型別的引數最好定義為nullable形式(簡寫可以?標註),這樣的引數賦值不正確的時候也不會報異常錯誤,只是引數值為null。特別是日期類,如果不是nullable型別,不傳值和傳錯值都會報異常

     上面例子中的t引數如果為   TestParameter?i=1&b=false&t=2016-37-06&s=test&g=E816F0B7-2FB7-47D9-84ED-119F58C9BEC5或者

 http://192.168.1.135:1507/mobileapi/test/TestParameter?i=1&b=false&t=&s=test&g=E816F0B7-2FB7-47D9-84ED-119F58C9BEC5,都會報錯,如果定義為nullable型別就一切正常:   public ResultData TestParameter(int i, string s, bool b, DateTime? t, Guid? g = null)。

2. 複雜型別傳遞

 GET請求中複雜型別的傳遞需要FormUriAttribute屬性配合,並且每個API可以有多個FromUri標示的引數,也可以在post請求中使用此屬性標註的引數。

 1  [HttpGet]
 2         public ResultData TestParameter2([FromUri]List<int> ids, [FromUri]User a)
 3         {
 4             try
 5             {
 6                 var data = new { ids=ids, name=a.name };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

   測試程式碼:

 1 function TestParameter2() {
 2     var v = { ids: [1, 2, 3], name='test', age=4, weight=100 };
 3     $.ajax({
 4         type: "get",
 5         url: host + "/mobileapi/test/TestParameter2",
 6         dataType: "text",
 7         data: { "": v },
 8         beforeSend: function (request) {
 9             request.setRequestHeader("token", $("#token").val());
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("報錯無語");
16         }
17     });
18 }

   測試結果:GET請求中可以利用FromUri屬性傳遞複雜型別

 

    POST請求中複雜型別的傳遞需要FormBodyAttribute屬性配合,並且每個API有且僅有一個FromBody標示的引數,並且只能在post請求中使用。

 1  [HttpPost]
 2         public ResultData TestParameter2([FromBody]List<int> ids, [FromBody]List<int> ids2)
 3         {
 4             try
 5             {
 6                 var data = new { ids=ids, ids2=ids2 };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

 

 1         [HttpPost]
 2         public ResultData TestParameter2([FromBody]List<int> ids, [FromBody]ProductData pd)
 3         {
 4             try
 5             {
 6                 var data = new { ids = ids, pd = pd };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

以上兩個示例中程式碼編譯不報錯,可以正常編譯,但是請求會報錯,報錯資訊如下:

   測試程式碼:

 1 function TestParameter2() {
 2     var v = { ids: [1, 2, 3], ids2: [4, 5, 6] };
 3     $.ajax({
 4         type: "post",
 5         url: host + "/mobileapi/test/TestParameter2",
 6         dataType: "text",
 7         data: { "": v },
 8         beforeSend: function (request) {
 9             request.setRequestHeader("token", $("#token").val());
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("報錯無語");
16         }
17     });
18 }

   

 1 function TestParameter2() {
 2     var v = { "ids": [1, 2, 3], pd: { barcode: "ddddd" } };
 3     $.ajax({
 4         type: "post",
 5         url: host + "/mobileapi/test/TestParameter2",
 6         dataType: "text",
 7         data: { "": v },
 8         beforeSend: function (request) {
 9             request.setRequestHeader("token", $("#token").val());
10         },
11         success: function (data) {
12             alert(data);
13         },
14         error: function (x, y, z) {
15             alert("報錯無語");
16         }
17     });
18 }

測試結果:

 以上兩組測試都會報無法將多個引數繫結到請求的內容異常

3. 陣列引數的正確傳遞

   調整一下API,只接收一個FromBody引數,併為List<int>

 1         [HttpPost]
 2         public ResultData TestParameter2([FromBody]List<int> ids)
 3         {
 4             try
 5             {
 6                 var data = new { ids = ids, pd = pd };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

測試一:

 1 //TestParameter
 2 function TestParameter2() {
 3     var v = { "ids": [1, 2, 3] };
 4     $.ajax({
 5         type: "post",
 6         url: host + "/mobileapi/test/TestParameter2",
 7         dataType: "text",
 8         data: { "": v },
 9         beforeSend: function (request) {
10             request.setRequestHeader("token", $("#token").val());
11         },
12         success: function (data) {
13             alert(data);
14         },
15         error: function (x, y, z) {
16             alert("報錯無語");
17         }
18     });
19 }

測試一的結果:

測試二:

 1 //TestParameter
 2 function TestParameter2() {
 3     var v = { "": [1, 2, 3] };
 4     $.ajax({
 5         type: "post",
 6         url: host + "/mobileapi/test/TestParameter2",
 7         dataType: "text",
 8         data: { "": v },
 9         beforeSend: function (request) {
10             request.setRequestHeader("token", $("#token").val());
11         },
12         success: function (data) {
13             alert(data);
14         },
15         error: function (x, y, z) {
16             alert("報錯無語");
17         }
18     });
19 }

測試二結果:

分析: 經過測試一和測試二結果觀察,資料傳遞格式不能陣列名,其實這個不是陣列傳遞的問題,而是WEB API frombody引數的一種約定,所以每個API只能有一個frombody格式的引數,因為這樣的引數不會依據屬性名對應解析,而是把body中傳送的所有資料解析成一個物件,所以無法定義多個 frombody 格式引數。

結論: 經過frombody修飾的引數只能有一個,並且經過frombody修飾的引數無論是簡單型別,還是複雜型別(包括陣列,list,系統class和自定義class等),傳參都不需要屬性名,屬性名必需為空字串(“”),否則無法解析,引數永遠為null

引數格式的列舉:

簡單型別json引數格式: {"":1} 、{"":1.0} 、{"":"test"} 、{"":"C"} 、{"":"2016-03-10"} 、{"":"BC069BF1-1382-4C5D-B3B1-9643F3F94A9D"} 

類定義如下:

1     public class User
2     {
3         public string Name { get; set; }
4         public int Age { get; set; }
5         public DateTime? Birthday { get; set; }
6     }

類型別json引數格式:{name:"Tom", age:18, birthday:"2016-03-10"}

4. FromUrl引數和FromBody引數混合使用,這種混合使用規則參照以上使用方法,沒難度。

 1         [HttpPost]
 2         public ResultData TestParameter2(int i, string s, [FromUri]bool b, DateTime? t, Guid? g = null, [FromBody]List<int> ids = null)
 3         {
 4             try
 5             {
 6                 var data = new { ids = ids };
 7                 return new ResultData(data);
 8             }
 9             catch (Exception ex)
10             {
11                 throw ex;
12                 //return new ResultData(ResultType.SystemException, ex.Message);
13             }
14         }

 

此篇到此結束,歡迎大家討論!

 補充:API中Action引數傳遞方式總結說明

    1. 引數不帶任何屬性標籤   Action F1(string a, string b)

         querystring中必需含有 a和b查詢字串   url?a=1&b=, 即使b沒有值,也必需攜帶,否則報404錯誤,找不到符合請求的Action

    2. 引數帶有fromuri屬性  Action F2(string a, [FromUri]stirng b)

   querystring中必需含有 a查詢字串,b可以不帶,因為被屬性[FromUri]修飾,例: url?a=1&b=或url?a=1都能匹配到F2 action, b沒有值可以不攜帶,但引數a必需攜帶

    3. 帶有fromuri屬性的引數可以有多個  Action F3(string a, [FromUri]stirng b,[FromUri]stirng c,[FromUri]stirng d) ,

   請求方法同2, 例如: url?a=1&b=2&d=3

    4. 帶有frombody屬性的引數只適應post請求,否則永遠為null ,並且可以與fromuri混合使用,但每個action,frombody屬性引數有且僅有一個,多個會報錯

           Action F4(string a, [FromUri]stirng b,[FromUri]stirng c,[FromBody]stirng d)

    使用方法同 2, 3

      這樣混合使用不報錯,也能正常執行,但需要混合使用的環境比較少,基本上95% Action只用fromuri或frombody就能完成,沒必要混合使用

      

 

相關文章