Web API(二)--靈活多樣的路由配置

1. 導言

路由系統是請求消息進入ASP.NET Web API消息處理管道的第一道屏障,其根本目的在於利用註冊的路由對請求的URL進行解析以確定目標HTTPController和Action的名稱,以及與目標Action方法某個參數進行綁定的路由變量。

WebService和WCF的協議都是soap協議,數據的序列化和反序列化都是soap的格式。而WebAPI是基於Http協議,請求和返回格式結果默認是 json格式,因此,比WCF更簡單、更通用,比 WebService 更節省流量、更簡潔。 Web API是在.NET Framework上構建RESTful應用程序的理想平臺,爲了更清楚弄懂WebAPI的路由配置,我們首先要了解HTTP協議和RESTful架構風格。

2. HTTP協議

HTTP協議(HyperText Transfer Protocol,超文本傳輸協議)是因特網上應用最爲廣泛的一種網絡傳輸協議,所有的WWW文件都必須遵守這個標準。我們在這裏緊列舉和本文關係密切的HTTP請求方法和HTTP狀態碼。

2.1 HTTP請求方法

根據HTTP標準,HTTP請求可以使用多種請求方法。

HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法,HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法,如下表所示:

序號 方法 描述
1 GET 請求指定的頁面信息,並返回實體主體。
2 HEAD 類似於get請求,只不過返回的響應中沒有具體的內容,用於獲取報頭
3 POST 向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會導致新的資源的建立和/或已有資源的修改。
4 PUT 從客戶端向服務器傳送的數據取代指定的文檔的內容。
5 DELETE 請求服務器刪除指定的頁面。
6 CONNECT HTTP/1.1協議中預留給能夠將連接改爲管道方式的代理服務器。
7 OPTIONS 允許客戶端查看服務器的性能。
8 TRACE 回顯服務器收到的請求,主要用於測試或診斷。

2.2 HTTP狀態碼

當瀏覽者訪問一個網頁時,瀏覽者的瀏覽器會向網頁所在服務器發出請求。當瀏覽器接收並顯示網頁前,此網頁所在的服務器會返回一個包含HTTP狀態碼(HTTP Status Code)的信息頭(server header)用以響應瀏覽器的請求。

常見的HTTP狀態碼如下表:

狀態碼 狀態碼英文名稱 中文描述
200 OK 請求成功。 一般用於GET與POST請求
301 Moved Permanently 永久移動。 請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今後任何新的請求都應使用新的URI代替
404 Not Found 服務器無法根據客戶端的請求找到資源(網頁)。 通過此代碼,網站設計人員可設置"您所請求的資源無法找到"的個性頁面
500 Internal Server Error 服務器內部錯誤,無法完成請求

3. RESTful介紹

在介紹RESTful之前,我們需瞭解什麼REST,他有那些特徵,以及REST成熟度模型。

3.1 REST介紹

REST是Representational State Transfer的縮寫,翻譯爲表象化狀態轉變或表述性狀態轉變,是Roy Fielding博士在2000年他的博士論文中提出來的一種軟件架構風格,它包含了一個分佈式超文本系統中對於組件、連接器和數據的約束。REST 是作爲互聯網自身架構的抽象而出現的,其關鍵在於所定義的架構上的各種約束。只有滿足這些約束,才能稱之爲符合 REST 架構風格。

3.2 REST系統的特徵

RESR系統包括6個特徵,如下所示:

  1. 客戶端-服務器結構(Client-Server)

通過一個統一的接口來分開客戶端和服務器,使得兩者可以獨立開發和演化。客戶端的實現可以簡化,而服務器可以更容易的滿足可伸縮性的要求;

  1. 無狀態(Stateless)

在不同的客戶端請求之間,服務器並不保存客戶端相關的上下文狀態信息。任何客戶端發出的每個請求都包含了服務器處理該請求所需的全部信息;

  1. 可緩存(Cachable)

客戶端可以緩存服務器返回的響應結果。服務器可以定義響應結果的緩存設置。

  1. 分層的系統(Layered System)

在分層的系統中,可能有中間服務器來處理安全策略和緩存等相關問題,以提高系統的可伸縮性。客戶端並不需要了解中間的這些層次的細節。

  1. 按需代碼(Code-On-Demand,可選)

服務器可以通過傳輸可執行代碼的方式來擴展或自定義客戶端的行爲。這是一個可選的約束。

  1. 統一接口(Uniform Interface)

該約束是 REST 服務的基礎,是客戶端和服務器之間的橋樑。該約束又包含下面 4 個子約束。

  1. **資源標識符:**每個資源都有各自的標識符。客戶端在請求時需要指定該標識符。在 REST 服務中,該標識符通常是 URI。客戶端所獲取的是資源的表達(representation),通常使用 XML 或 JSON 格式。
  2. 通過資源的表達來操縱資源:客戶端根據所得到的資源的表達中包含的信息來了解如何操縱資源,比如對資源進行修改或刪除。
  3. **自描述的消息:**每條消息都包含足夠的信息來描述如何處理該消息。
  4. **超媒體作爲應用狀態的引擎(HATEOAS):**客戶端通過服務器提供的超媒體內容中動態提供的動作來進行狀態轉換。

3.3 REST成熟度模型

Richardson 提出的 REST 成熟度模型。該模型把 REST 服務按照成熟度劃分成 4 個層次,我們常用到的就是Level1和Level2,如下圖所:
在這裏插入圖片描述
具體說明如下:

Level 0:Web 服務只是使用 HTTP 作爲傳輸方式,實際上只是遠程方法調用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬於此類。

**Level 1:**Web 服務引入了資源的概念。每個資源有對應的標識符和表達。

**Level 2:**Web 服務使用不同的 HTTP 方法來進行不同的操作,並且使用 HTTP 狀態碼來表示不同的結果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。

**Level 3:**Web 服務使用 HATEOAS。在資源的表達中包含了鏈接信息。客戶端可以根據鏈接來發現可以執行的動作。例如,客戶端通過訂單資源中包含的鏈接取消某一訂單,GET 請求被髮送去獲取該訂單。HATEOAS 的優點包括無需在客戶端代碼中寫入硬鏈接的 URL。此外,由於資源信息中包含可允許操作的鏈接,客戶端無需猜測在資源的當前狀態下執行何種操作。

3.4 RESTful

RESTful是一種常見的REST應用,是遵循REST風格的web服務,REST式的web服務是一種ROA(面向資源的架構)。

RESTful資源操作如下表:

http方法 資源操作 冪等 安全
GET SELECT
POST INSERT
PUT UPDATE
DELETE DELETE

注:冪等性:對同一REST接口的多次訪問,得到的資源狀態是相同的;安全性:對該REST接口訪問,不會使服務器端資源的狀態發生改變。

RESTful URL請求格式與傳統請求格式比較如下表所示:

傳統URL請求格式 RESTFul請求格式 描述
http:/localhost/user/query/1 GET http:/localhost/user/1 GET 根據用戶id查詢用戶數據
http:/localhost/user/save POST http:/localhost/user POST 新增用戶
http:/localhost/user/update POST http:/localhost/user PUT 修改用戶信息
http:/localhost/user/delete GET/POST http:/localhost/user DELETE 刪除用戶信息

4.Web API路由

路由的目的是用於解析請求的URL來確定Controller和Action。Web API默認路由是通過http的方法(get/post/put/delete)去匹配對應的action,也就是說webapi的默認路由並不需要指定action的名稱,當然,WebApi也支持MVC裏面的路由機制,但RestFul風格的服務要求請求的url裏面不能包含action,所以,在WebApi裏面是並不提倡使用MVC路由機制的。下邊通過例子介紹Web API路由原理以及使用。

4.1新建空的Web API應用程序

上篇博客介紹了手動搭建基本框架,這次我們通過VS2017提供的模板,新建空的WebAPI應用程序MyWebAPI2,構建過程中僅勾選Web API,如下圖所示。
在這裏插入圖片描述
創建完成,應用程序的目錄結構如下圖所示
在這裏插入圖片描述

4.2默認路由

App_Start文件夾下WebApiConfig.cs類用於註冊Web API路由,默認路由代碼如下:

public static void Register(HttpConfiguration config)
        {
           //默認路由
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

在Models文件夾增加一個Student類,代碼如下:

public class Student
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public string Sex { get; set; }
        public int Age { get; set; }
        public string Dept { get; set; }
    }

在Controllers文件夾中添加一個Web API控制類(v2.1),命名爲StudentController,並修改相關代碼,如下所示:

public class StudentController : ApiController
    {
        private static List<Student> studentList = new List<Student>()
        {
            new Student() {Id = "001", Name = "張三", Sex = "男", Age = 19, Dept = "軟件學院"},
            new Student() {Id = "002", Name = "李麗", Sex = "女", Age = 19, Dept = "資環學院"}
        };
        
        [HttpGet]
        public IEnumerable<Student> Get()
        {
            return studentList;
        }

        [HttpGet]
        public Student Get(string id)
        {
            List<Student> tempList = studentList.Where(p => p.Id == id).ToList();
            return tempList.Count==1?tempList.First():null ;
        }

        [HttpPost]
        public bool Post([FromBody]Student student)
        {
            if (student == null) return false;
            if (studentList.Where(p=>p.Id==student.Id).ToList().Count>0) return false;
            studentList.Add(student);
            return true;
        }

        [HttpPut]
        public bool Put(string id, [FromBody]Student student)
        {
            if (student == null) return false;
            List<Student> tempList = studentList.Where(p => p.Id == id).ToList();
            if (tempList.Count == 0) return false;
            Student originStudent = tempList[0];
            originStudent.Name = student.Name;
            originStudent.Sex = student.Sex;
            originStudent.Age = student.Age;
            originStudent.Dept = student.Dept;
            return true;
        }
        [HttpDelete]
        public bool Delete(string id)
        {
            List<Student> tempList = studentList.Where(p => p.Id == id).ToList();
            if (tempList.Count == 0) return false;
            studentList.Remove(tempList[0]);
            return true;
        }
    }

在實際項目中,增刪改查這些操作都是和數據庫打交道的,這裏爲了演示具體實現,用一個靜態數組存儲數據。運行程序,我們採用Fiddler工具進行測試調用。

測試一:調用Get()方法

功能說明:通過路由解析StudentController中的Get()Action,獲取所有學生列表。

URL:http://localhost:52317/api/student;HTTP方法:GET。如下圖所示:
在這裏插入圖片描述
測試結果:HTTP狀態碼爲200,獲取的JSON數據如下所示:

在這裏插入圖片描述

測試二:調用Get(string id)方法

功能說明:通過路由解析StudentController中的Get(string id)Action,根據學號獲取某個學生信息。

URL:http://localhost:52317/api/student/002;HTTP方法:GET。

測試結果:HTTP狀態碼爲200,獲取的JSON數據如下所示:
在這裏插入圖片描述

測試三:調用Post([FromBody]Student student)方法

功能說明:通過路由解析StudentController中的Post([FromBody]Student student)Action,插入一條數據,其中參數前添加[FromBody]是指該參數不是從URL中獲取,而是在RequestBody中獲取。

URL:http://localhost:52317/api/student;HTTP方法:POST。在RequestBody輸入Json格式的數據,如下圖所示:
在這裏插入圖片描述
測試結果:HTTP狀態碼爲200,返回的數據TRUE。重複測試一,獲取所有學生列表如下圖所示。
在這裏插入圖片描述

測試四:調用Put(string id, [FromBody]Student student)方法

功能說明:通過路由解析StudentController中的Put(string id, [FromBody]Student student)Action,實現通過學號更新信息,其中學號id是在Url中獲取。

URL:http://localhost:52317/api/student/002;HTTP方法:PUT。

測試結果:HTTP狀態碼爲200,返回的數據TRUE。

測試五:調用Delete(string id)方法

功能說明:通過路由解析StudentController中的Delete(string id)Action,刪除某學生信息,其中學號id是在Url中獲取。

URL:http://localhost:52317/api/student/002;HTTP方法:DELETE。

測試結果:HTTP狀態碼爲200,返回的數據TRUE。

總結:通過上邊測試,我們可以到URL中沒有用到Action,默認路由就是通過參數和HTTP方法匹配Controller中的Action。

4.3自定義路由

在實際項目中,如果http請求的類型相同,且請求參數相同(如Get),這個時候按照默認路由肯定會出問題,具體出現什麼問題,我們在這做個測試:

在StudentController中添加一個Action,用於查詢某個學院的所有同學,代碼如下:

[HttpGet]
        public IEnumerable<Student> GetByDept(string id)
        {
            List<Student> tempList = studentList.Where(p => p.Dept == id).ToList();
            return tempList;
        }

這時,運行程序,輸入URL:http://localhost:52317/api/student/資環學院;HTTP方法:GET。測試結果發現返回的結果null,跟蹤測試代碼發現該URL調用的Get(string id)這個Action。

那麼問題就來了,怎麼才能調用這個Action呢?其中有兩種方法一種是自定義路由,就像MVC那樣在模板中增加Action,另一種爲通過Web API路由特性實現。

在App_Start文件夾下WebApiConfig.cs類中添加ActionAPI路由模板,代碼如下:

public static void Register(HttpConfiguration config)
        {
           //默認路由
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            //自定義路由
            config.Routes.MapHttpRoute(
                name: "ActionApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

運行程序,輸入URL:http://localhost:52317/api/student/GetByDept/資環學院;HTTP方法:GET。運行結果如下:
在這裏插入圖片描述
注:若出現中文亂碼,解決途徑請參考Fiddler抓包中文亂碼問題。

由此可知通過自定義路由可以解決該問題,但是不符合RESTful風格,所有不提倡使用該方法。

4.4特性路由

默認路由解決不了的訪問,可以通過Web API的路由特性解決。

啓動路由特性:在App_Start文件夾下WebApiConfig.cs類中啓用特性路由,代碼如下:

public static void Register(HttpConfiguration config)
        {
            //啓用Web API特性路由
            config.MapHttpAttributeRoutes();

            //默認路由
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            //自定義路由
            config.Routes.MapHttpRoute(
                name: "ActionApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }

修改StudentController中GetByDept代碼如下:

[Route("student/GetByDept/{dept}")]
        [HttpGet]
        public IEnumerable<Student> GetByDept(string dept)
        {
            List<Student> tempList = studentList.Where(p => p.Dept == dept).ToList();
            return tempList;
        }

運行程序,輸入URL:http://localhost:52317/student/GetByDept/軟件學院;HTTP方法:GET。運行結果如下:
在這裏插入圖片描述
通過此例,可以看到Web API特性路由非常靈活方便,具體使用要結合實際項目靈活運用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章