.net 溫故知新【12】:Asp.Net Core WebAPI 中的Rest風格

2023-07-13 12:01:16

RPC

RPC(Remote Procedure Call),遠端過程呼叫),這種RPC形式的API組織形態是類和方法的形式。所以API的請求往往是一個動詞用來標識介面的意思,比如 https://xxxx/GetStudent?id=1https://xxxx/AddStudent 這種風格,並且往往沒有規範需要我們去檢視介面定義檔案。HTTP方法基本只用GET和POST,沒有使用HTTP的其它謂詞設計比較簡單。

Rest

Rest:按照Http的語意來使用HTTP協定的一種風格,Rest全稱Representational State Transfer(表現層狀態轉換)。他是一種規範或者設計風格而不是特別的技術。REST形式的API組織形態是資源和實體,請求的路由可以看出對資源的存取,規範統一介面自解釋。
比如 https://xxxx/Student/1 用Get方法呼叫就是獲取編號為1的學生。 https://xxxx/Student/1 用Delete呼叫就是刪除編號為1的學生,用delete呼叫就是刪除該學生。
在HTTP中這些呼叫方法GET、POST、PUT、PATCH、DELETE 即HTTP謂詞。GET用來獲取資源,POST用來新建資源,PUT用來更新指定資源,PATCH用來批次更新資源,DELETE用來刪除資源,通過謂詞來表示請求動作或者意圖,通過url定位資源。
在請求中GET、PUT、DELETE 請求是冪等的,也就是說可以重試請求。而POST不是冪等,因為POST意思是新增資料。
在Rest風格中使用狀態碼來標識返回結果,其中常用200、201、400、401、404、410、500等。

ASP.NET Core WebAPI介面

在ASP.NET WebAPI中我們也能看到Rest的風格,理想很豐滿,顯示很骨幹。如果我們嚴格的按照Rest風格設計介面的話,需要對技術人員有很高的要求,需要去劃分不同業務不同的資源定位,而且有些業務也找不到準確的謂詞去定義,響應狀態碼有限無法表達準確的意思,或者是時間上來不及等等原因。
而且這種方式更符合國外語言表達的方式,不太適合我國寶寶體質。
所以我們在設計介面的時候不用非要用Rest風格,我們可以靠近或者在特定的更適合使用Rest介面系統中使用。
本篇我們結合Rest看下介面如何設計和互動。

  • 在http介面請求中有三種方式傳遞引數或者資料。
  1. URL:資源定位,也就是Rest風格,在請求的url中包含資訊,比如https://xxxx/Student/1 1就是學生編號。
  2. QueryString: URL之外的額外資訊,比如RPC中https://xxxx/GetStudent?id=1 id=1就是QueryString
  3. 請求報文體:供PUT、POST提交提供資料,請求體有多種格式application/x-www-form-urlencoded、multipart/form-data、application/json、text/plain、application/xml。
  • 返回狀態碼
    在RPC中Post請求我們習慣如果請求已經在伺服器處理,不管處理結果是否正確,我們都返回200狀態碼。然後在返回資料中用其它資訊來標識業務結果。比如{code:1,msg:"成功"}或者{code:0,msg:"失敗"}
    而在Rest 中Post通常用201返回新增成功,delete 刪除的資料不存在返回404,但是404大家知道可能也許是url錯誤,所以表訴不清。
    因此我們在實際設計中可能會進行Rest裁剪,我們既使用RPC的返回結果,同時多用準確的狀態碼,不用什麼都返回200。
    使用RPC風格,儘量使用合理謂詞,不知道使用什麼謂詞的時候就用POST,Get Delete引數儘量用資源定位URL,業務錯誤伺服器端返回合適的狀體嗎,不知道返回什麼就返回400,如果請求處理成功就用200同時返回結果資料。

在上一篇中遺留的這個問題 .net 溫故知新【11】:Asp.Net Core WebAPI 入門使用及介紹

所以我們在Controller中Route設定為[Controller]則不管方法介面名稱是什麼,仍然以Rest的方式存取。

    [Route("[controller]")]
    [ApiController]
    public class RestCutController : ControllerBase
    {
        [HttpGet]
        public IEnumerable<string> GetStudents()
        {
            //獲取所有學生
            return new string[] { "student1", "student2" };
        }

        [HttpGet("{id}")]
        public string GetStudent(int id)
        {
            //獲取id的學生
            return "student"+id;
        }

        [HttpPost]
        public void PostStudent([FromBody] string value)
        {
            //新增
        }

        [HttpPut("{id}")]
        public void PutStudent(int id, [FromBody] string value)
        {
            //修改
        }

        [HttpDelete("{id}")]
        public void DeleteStudent(int id)
        {
            //刪除id學生
        }
    }

當我們修改Rout按照RPC方式,[Route("[controller]/[action]")] 執行後發現swagger展示的介面方式就改變了。並且保留了引數URL的方式。

關於返回狀態碼的問題可以有兩種方式,一種是直接在ControllerBase.Response 響應中指定返回狀態碼。

        [HttpDelete("{id}")]
        public string DeleteStudent(int id)
        {
            //刪除id學生
            if (id == 1)
            {
                return "刪除成功";
            }
            else {

                Response.StatusCode = 404;
                return "未找到!";
            }
        }

另外一種方式就是返回泛型ActionResult<string>,其中OKNotFound是繼承自ActionResult然後隱式轉換到泛型,也可以直接返回IActionResult或者ActionResult但是型別不確定這樣swagger檔案就不會解析出返回值,所以我們用ActionResult泛型。

        [HttpDelete("{id}")]
        public ActionResult<string> DeleteStudent(int id)
        {
            //刪除id學生
            if (id == 1)
            {
                return Ok("刪除成功");
            }
            else
            {
                return NotFound("未找到!");
            }
        }

        [HttpDelete("{id}")]
        public ActionResult DeleteStudent(int id) //返回ActionResult
        {
            //刪除id學生
            if (id == 1)
            {
                return Ok("刪除成功");
            }
            else
            {
                return NotFound("未找到!");
            }
        }

最後我們在總結下關於API引數獲取的方式,在 [HttpGet("{id}")]中我們看到有{id},這個就是預留位置,從RUL中獲取,不光可以設定預留位置還可以設定路徑的其它值,甚至可以隨意組織,只要我們的引數明和預留位置相同就行。

        [HttpDelete("number/{id}/Name/{name}")] //自己組織的URL
        public ActionResult<string> DeleteStudent(int id,string name)
        {
            //刪除id學生
            if (id == 1)
            {
                return Ok("刪除成功");
            }
            else
            {
                return NotFound("未找到!");
            }
        }

當然也可以使用[FromRoute]從route獲取,另外我們還有一些Attribute用於從不同的地方獲取引數,比如從QueryString獲取。那麼我的請求URL就應該是/RestCut/DeleteStudent?id=1

        [HttpDelete]
        public ActionResult<string> DeleteStudent([FromQuery] int id)
        {
            //刪除id學生
            if (id == 1)
            {
                return Ok("刪除成功");
            }
            else
            {
                return NotFound("未找到!");
            }
        }

最後還有[FromHeader][FromForm][FromBody]這些獲取引數的方式,不清楚的使用的時候查詢就行了。