1. 程式人生 > >WebApi(五)【介面返回值不困惑:返回值型別詳解】

WebApi(五)【介面返回值不困惑:返回值型別詳解】

使用過Webapi的園友應該都知道,Webapi的介面返回值主要有四種類型

void無返回值
IHttpActionResult
HttpResponseMessage
自定義型別

此篇就圍繞這四塊分別來看看它們的使用。

一、void無返回值

void關鍵字我們都不陌生,它申明方法沒有返回值。它的使用也很簡單,我們來看一個示例就能明白。

 public class ORDER
    {
        public string ID { get; set; }

        public string NO { get; set; }

        public
string NAME { get; set; } public string DESC { get; set; } }
public class OrderController : ApiController
    {
        [HttpPost]
        public void SaveOrder(ORDER name)
        { 
            //處理業務邏輯

        }
    }

在Web裡面呼叫

$(function () {
    $.ajax({
        type: 'post',
        url: 'http://localhost:21528/api/Order/SaveOrder'
, data: { ID: "aaa", NAME: "test" }, success: function (data, status) { alert(data); } }); });

得到結果
這裡寫圖片描述
可以看到,使用void申明的方法,在success方法裡面得不到返回值,並且會返回http狀態碼204,告訴客戶端此請求沒有返回值。

二、IHttpActionResult

IHttpActionResult型別是WebApi裡面非常重要的一種返回值型別。下面博主就根據平時在專案裡面使用最多的幾種方式來講解下這種型別的返回值的一些用法。

1、Json<T>(T content)

使用MVC開發過的朋友一定記得,在MVC裡面,請求資料的介面的返回值型別大部分使用的是JsonResult,在MVC裡面你一定也寫過類似這樣的介面:

public JsonResult GetResult()
        {
            return Json(new { }, JsonRequestBehavior.AllowGet);
        }

那麼,在WebAPI裡面是否也存在類似的用法呢。呵呵,在這點上面,微軟總是貼心的。在WebApiApiController這個抽象類裡面,為我們封裝了Json<T>(T content)這個方法,它的用法和MVC裡面的JsonResult基本類似。我們通過一個例子來說明它的用法:

[HttpGet]
        public IHttpActionResult GetOrder()
        {
            var lstRes = new List<ORDER>(); 

            //實際專案中,通過後臺取到集合賦值給lstRes變數。這裡只是測試。
            lstRes.Add(new ORDER() { ID = "aaaa", NO = "111", NAME = "111", DESC = "1111" });
            lstRes.Add(new ORDER() { ID = "bbbb", NO = "222", NAME = "222", DESC = "2222" });

            return Json<List<ORDER>>(lstRes);
        }

看到這個程式碼,有人就疑惑了,我們定義的返回值型別是IHttpActionResult型別,直接返回Json<T>(T content)這樣可行麼?我們將Json轉到定義看看:

protected internal JsonResult<T> Json<T>(T content); 

我們繼續將JsonResult<T>轉到定義
這裡寫圖片描述
原來JsonResult<T>是實現了IHttpActionResult介面的,難怪可以直接返回呢。

知道了這個,我們直接在Web裡面通過ajax請求來呼叫:

$(function () {
    $.ajax({
        type: 'get',
        url: 'http://localhost:21528/api/Order/GetOrder',
        data: {},
        success: function (data, status) {
            alert(data);
        }
    });
});

來看結果:
這裡寫圖片描述

既然實體類可以直接這樣傳遞,那麼如果我們想要傳遞一些匿名型別呢,因為很多情況下,我們需要返回到前端的物件都沒有對應的實體來對應,如果我們想要返回匿名物件怎麼辦呢?我們知道,這裡的Json<T>(T content)必須要傳一個對應的泛型型別,如果是匿名型別這裡肯定不好傳。還好有我們的object型別,當然你可以使用dynamic,我們來試一把。

[HttpGet]
        public IHttpActionResult GetOrder()
        {

            return Json<dynamic>(new { AA = "", BB = "cc" });
        }

同樣的來看測試結果:
這裡寫圖片描述

2、Ok()Ok<T>(T content)

除了Json<T>(T content),在ApiController裡面還有另外一個比較常用的方法:Ok()。同樣,我們將Ok()轉到定義

protected internal virtual OkResult Ok();

OkResult轉到定義
這裡寫圖片描述

有了這個作為基礎,我們就可以放心大膽的使用了。

[HttpGet]
 public IHttpActionResult GetOKResult()
 {
   return Ok();
 }

得到結果

這裡寫圖片描述

如果返回Ok(),就表示不向客戶端返回任何資訊,只告訴客戶端請求成功。

除了Ok()之外,還有另外一個過載Ok<T>(T content)

[HttpGet]
 public IHttpActionResult GetOKResult(string name)
 {
  return Ok<string>(name);
 }

這裡寫圖片描述

這種用法和Json<T>(T content)比較類似,如果你非要問這兩者有什麼區別,或者說怎麼選擇兩者。那麼我的理解是如果是返回實體或者實體集合,建議使用Json<T>(T content),如果是返回基礎型別(如int、string等),使用Ok<T>(T content)

3、NotFound()

當需要向客戶端返回找不到記錄時,有時需要用到NotFound()方法。

protected internal virtual NotFoundResult NotFound();

這裡寫圖片描述

來看看它的使用場景

        [HttpGet]
        public IHttpActionResult GetNotFoundResult(string id)
        {
            var lstRes = new List<ORDER>();

            //實際專案中,通過後臺取到集合賦值給lstRes變數。這裡只是測試。
            lstRes.Add(new ORDER() { ID = "aaaa", NO = "111", NAME = "111", DESC = "1111" });
            lstRes.Add(new ORDER() { ID = "bbbb", NO = "222", NAME = "222", DESC = "2222" });
            var oFind = lstRes.FirstOrDefault(x => x.ID == id) ;
            if (oFind == null)
            {
                return NotFound();
            }
            else
            {
                return Json<ORDER>(oFind);
            }
        }
$(function () {
    $.ajax({
        type: 'get',
        url: 'http://localhost:21528/api/Order/GetNotFoundResult',
        data: { id :"cccc" },
        success: function (data, status) {
            alert(data);
        }
    });
});

得到結果
這裡寫圖片描述

NotFound()方法會返回一個404的錯誤到客戶端。

4、其他

其他還有一些方法,都有它特定的用途。在此貼出來。

4.1、Content<T>(HttpStatusCode statusCode, T value)

 [HttpGet]
public IHttpActionResult GetContentResult()
{
 return Content<string>(HttpStatusCode.OK, "OK");
}

向客戶端返回值和http狀態碼。

4.2、BadRequest()

       [HttpGet]
        public IHttpActionResult GetBadRequest(ORDER order)
        {
            if (string.IsNullOrEmpty(order.ID))
                return BadRequest();
            return Ok();
        }

向客戶端返回400的http錯誤。

4.3、Redirect(string location)

        [HttpGet]
        public IHttpActionResult RedirectResult()
        {
            return Redirect("http://localhost:21528/api/Order/GetContentResult");
        }

將請求重定向到其他地方。

5、自定義IHttpActionResult介面的實現

上面介紹了一些系統內建的常用的實現IHttpActionResult介面的方法。如果我們需要自定義IHttpActionResult的返回呢?

在介紹之前,我們有必要先來看看IHttpActionResult型別的定義,將IHttpActionResult轉到定義可以看到:

namespace System.Web.Http
{
    // 摘要: 
    //     Defines a command that asynchronously creates an System.Net.Http.HttpResponseMessage.
    public interface IHttpActionResult
    {
        // 摘要: 
        //     Creates an System.Net.Http.HttpResponseMessage asynchronously.
        //
        // 引數: 
        //   cancellationToken:
        //     The token to monitor for cancellation requests.
        //
        // 返回結果: 
        //     A task that, when completed, contains the System.Net.Http.HttpResponseMessage.
        Task<System.Net.Http.HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken);
    }
}

這個介面包含唯一的一個方法ExecuteAsync(),此方法將以非同步方式建立一個HttpResponseMessage例項返回給客戶端。

有了這個作為基礎,下面,我們自定義一個bootstrapTable服務端分頁的子類去展示自定義IHttpActionResult的用法。

首先,自定義一個實現類

public class PageResult : IHttpActionResult
    {
        object _value;
        HttpRequestMessage _request;

        public PageResult(object value, HttpRequestMessage request)
        {
            _value = value;
            _request = request;
        }

        public Task<HttpResponseMessage> ExecuteAsync(System.Threading.CancellationToken cancellationToken)
        {
            var response = new HttpResponseMessage()
            {
                Content = new ObjectContent(typeof(object), _value, new JsonMediaTypeFormatter()),
                RequestMessage = _request
            };
            return Task.FromResult(response);
        }
    }

然後,在API接口裡面返回PageResult物件

[HttpGet]
        public IHttpActionResult GetPageRow(int limit, int offset)
        {
            var lstRes = new List<ORDER>();

            //實際專案中,通過後臺取到集合賦值給lstRes變數。這裡只是測試。
            lstRes.Add(new ORDER() { ID = "aaaa", NO = "111", NAME = "111", DESC = "1111" });
            lstRes.Add(new ORDER() { ID = "bbbb", NO = "222", NAME = "222", DESC = "2222" });

            var oData = new { total = lstRes.Count, rows = lstRes.Skip(offset).Take(limit).ToList() };
            return new PageResult(oData, Request);
        }

最好,ajax呼叫

$(function () {
    $.ajax({
        type: 'get',
        url: 'http://localhost:21528/api/Order/GetPageRow',
        data: { limit:1,offset:1},
        success: function (data, status) {
            alert(data);
        }
    });
});

得到結果

這裡寫圖片描述

三、HttpResponseMessage

在上文自定義IHttpActionResult返回型別的時候,提到過HttpResponseMessage這個物件。它表示向客戶端返回一個http響應的訊息物件(包含http狀態碼和需要返回客戶端的訊息)。這個物件也有它獨特的使用場景:需要向客戶端返回HttpResponse時就要用到這個物件。以匯出為例,由於需要將匯出的Excel檔案輸出到客戶端瀏覽器,Webapi的服務端需要向Web的客戶端輸出檔案流,這個時候一般的IHttpActionResult物件不方便解決這個問題,於是HttpReponseMessage派上了用場。我們來看看它的使用示例。

public HttpResponseMessage Export()
        {
            //取資料
            var lstRes = OrderBLL.Export();

            //向Excel裡面填充資料
            HSSFWorkbook workbook = new HSSFWorkbook();
            CreateAndFillSheet(workbook, lstRes);

            //儲存到服務
            var fileName = "Excel" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xls";
            var strPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Data\" + fileName);
            using (FileStream fs = new FileStream(strPath, FileMode.Create))
            {
                workbook.Write(fs);
                using (MemoryStream ms = new MemoryStream())
                {
                    workbook.Write(ms);
                }
            }

            //輸出到瀏覽器
            try
            {
                var stream = new FileStream(strPath, FileMode.Open);
                HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StreamContent(stream);
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = fileName
                };

                return response;
            }
            catch
            {
                return new HttpResponseMessage(HttpStatusCode.NoContent);
            }
        }

將檔案流儲存在StreamContent物件裡面,然後輸出到瀏覽器。在瀏覽器端即可將Excel輸出。

四、自定義型別

以上幾種返回值型別能解決我們大部分返回值的問題,當然,你也可以將webapi的介面和普通方法一樣,返回任意的型別,WebApi會自動序列化你自定義任何返回型別,然後將序列化的值寫到響應正文裡,狀態碼統一返回200。比如:

[HttpGet]
        public object GetOther()
        {
            var lstRes = new List<ORDER>();

            //實際專案中,通過後臺取到集合賦值給lstRes變數。這裡只是測試。
            lstRes.Add(new ORDER() { ID = "aaaa", NO = "111", NAME = "111", DESC = "1111" });
            lstRes.Add(new ORDER() { ID = "bbbb", NO = "222", NAME = "222", DESC = "2222" });

            return lstRes;
        }

得到結果
這裡寫圖片描述

和上面的JsonOk等用法在效果上面沒有太大區別。

五、總結

以上通過四個方面詳細分享了下WebApi裡面返回值的常見用法,不能說哪種方式最好,因為每種方式都有其特定的使用場景。博主覺得為了規範WebApi介面,對於一般介面的返回值,儘量使用IHttpActionResult型別作為返回值,畢竟是微軟內建的東西,可能為我們考慮了很多我們考慮不到的東西。當然,你可能會覺得麻煩,你可能會說直接和普通方法一樣來使用不是更爽,博主當初也有這種想法,可是學習微軟的東西多了之後發現很多東西還是遵守一定的標準比較好,至少維護起來方便。這就像博主最近正在努力學習的WebApi+oData一樣,為什麼要搞這麼一套標準性的東西,還不是為了更加方便地規範Restful風格。