1.導(dǎo)言
路由系統(tǒng)是請求消息進入ASP.NET Web API消息處理管道的第一道屏障,其根本目的在于利用注冊的路由對請求的URL進行解析以確定目標(biāo)HTTPController和Action的名稱,以及與目標(biāo)Action方法某個參數(shù)進行綁定的路由變量。
WebService和WCF的協(xié)議都是soap協(xié)議,數(shù)據(jù)的序列化和反序列化都是soap的格式。而WebAPI是基于Http協(xié)議,請求和返回格式結(jié)果默認(rèn)是 json格式,因此,比WCF更簡單、更通用,比 WebService 更節(jié)省流量、更簡潔。 Web API是在.NET Framework上構(gòu)建RESTful應(yīng)用程序的理想平臺,為了更清楚弄懂WebAPI的路由配置,我們首先要了解HTTP協(xié)議和RESTful架構(gòu)風(fēng)格。
2.HTTP協(xié)議
HTTP協(xié)議(HyperText Transfer Protocol,超文本傳輸協(xié)議)是因特網(wǎng)上應(yīng)用最為廣泛的一種網(wǎng)絡(luò)傳輸協(xié)議,所有的WWW文件都必須遵守這個標(biāo)準(zhǔn)。我們在這里緊列舉和本文關(guān)系密切的HTTP請求方法和HTTP狀態(tài)碼。
2.1 HTTP請求方法
根據(jù)HTTP標(biāo)準(zhǔn),HTTP請求可以使用多種請求方法。
HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法,HTTP1.1新增了五種請求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法,如下表所示:
| 序號 | 方法 | 描述 |
|---|---|---|
| 1 | GET | 請求指定的頁面信息,并返回實體主體。 |
| 2 | HEAD | 類似于get請求,只不過返回的響應(yīng)中沒有具體的內(nèi)容,用于獲取報頭 |
| 3 | POST | 向指定資源提交數(shù)據(jù)進行處理請求(例如提交表單或者上傳文件)。數(shù)據(jù)被包含在請求體中。POST請求可能會導(dǎo)致新的資源的建立和/或已有資源的修改。 |
| 4 | PUT | 從客戶端向服務(wù)器傳送的數(shù)據(jù)取代指定的文檔的內(nèi)容。 |
| 5 | DELETE | 請求服務(wù)器刪除指定的頁面。 |
| 6 | CONNECT | HTTP/1.1協(xié)議中預(yù)留給能夠?qū)⑦B接改為管道方式的代理服務(wù)器。 |
| 7 | OPTIONS | 允許客戶端查看服務(wù)器的性能。 |
| 8 | TRACE | 回顯服務(wù)器收到的請求,主要用于測試或診斷。 |
2.2 HTTP狀態(tài)碼
當(dāng)瀏覽者訪問一個網(wǎng)頁時,瀏覽者的瀏覽器會向網(wǎng)頁所在服務(wù)器發(fā)出請求。當(dāng)瀏覽器接收并顯示網(wǎng)頁前,此網(wǎng)頁所在的服務(wù)器會返回一個包含HTTP狀態(tài)碼(HTTP Status Code)的信息頭(server header)用以響應(yīng)瀏覽器的請求。
常見的HTTP狀態(tài)碼如下表:
| 狀態(tài)碼 | 狀態(tài)碼英文名稱 | 中文描述 |
|---|---|---|
| 200 | OK | 請求成功。一般用于GET與POST請求 |
| 301 | Moved Permanently | 永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今后任何新的請求都應(yīng)使用新的URI代替 |
| 404 | Not Found | 服務(wù)器無法根據(jù)客戶端的請求找到資源(網(wǎng)頁)。通過此代碼,網(wǎng)站設(shè)計人員可設(shè)置"您所請求的資源無法找到"的個性頁面 |
| 500 | Internal Server Error | 服務(wù)器內(nèi)部錯誤,無法完成請求 |
3.RESTful介紹
在介紹RESTful之前,我們需了解什么REST,他有那些特征,以及REST成熟度模型。
3.1 REST介紹
REST是Representational State Transfer的縮寫,翻譯為表象化狀態(tài)轉(zhuǎn)變或表述性狀態(tài)轉(zhuǎn)變,是Roy Fielding博士在2000年他的博士論文中提出來的一種軟件架構(gòu)風(fēng)格,它包含了一個分布式超文本系統(tǒng)中對于組件、連接器和數(shù)據(jù)的約束。REST 是作為互聯(lián)網(wǎng)自身架構(gòu)的抽象而出現(xiàn)的,其關(guān)鍵在于所定義的架構(gòu)上的各種約束。只有滿足這些約束,才能稱之為符合 REST 架構(gòu)風(fēng)格。
3.2 REST系統(tǒng)的特征
RESR系統(tǒng)包括6個特征,如下所示:
客戶端-服務(wù)器結(jié)構(gòu)(Client-Server)
通過一個統(tǒng)一的接口來分開客戶端和服務(wù)器,使得兩者可以獨立開發(fā)和演化。客戶端的實現(xiàn)可以簡化,而服務(wù)器可以更容易的滿足可伸縮性的要求;無狀態(tài)(Stateless)
在不同的客戶端請求之間,服務(wù)器并不保存客戶端相關(guān)的上下文狀態(tài)信息。任何客戶端發(fā)出的每個請求都包含了服務(wù)器處理該請求所需的全部信息;可緩存(Cachable)
客戶端可以緩存服務(wù)器返回的響應(yīng)結(jié)果。服務(wù)器可以定義響應(yīng)結(jié)果的緩存設(shè)置。分層的系統(tǒng)(Layered System)
在分層的系統(tǒng)中,可能有中間服務(wù)器來處理安全策略和緩存等相關(guān)問題,以提高系統(tǒng)的可伸縮性??蛻舳瞬⒉恍枰私庵虚g的這些層次的細(xì)節(jié)。按需代碼(Code-On-Demand,可選)
服務(wù)器可以通過傳輸可執(zhí)行代碼的方式來擴展或自定義客戶端的行為。這是一個可選的約束。統(tǒng)一接口(Uniform Interface)
該約束是 REST 服務(wù)的基礎(chǔ),是客戶端和服務(wù)器之間的橋梁。該約束又包含下面 4 個子約束。資源標(biāo)識符:每個資源都有各自的標(biāo)識符??蛻舳嗽谡埱髸r需要指定該標(biāo)識符。在 REST 服務(wù)中,該標(biāo)識符通常是 URI??蛻舳怂@取的是資源的表達(dá)(representation),通常使用 XML 或 JSON 格式。
通過資源的表達(dá)來操縱資源:客戶端根據(jù)所得到的資源的表達(dá)中包含的信息來了解如何操縱資源,比如對資源進行修改或刪除。
自描述的消息:每條消息都包含足夠的信息來描述如何處理該消息。
超媒體作為應(yīng)用狀態(tài)的引擎(HATEOAS):客戶端通過服務(wù)器提供的超媒體內(nèi)容中動態(tài)提供的動作來進行狀態(tài)轉(zhuǎn)換。、
3.3 REST成熟度模型
Richardson 提出的 REST 成熟度模型。該模型把 REST 服務(wù)按照成熟度劃分成 4 個層次,我們常用到的就是Level1和Level2,如下圖所:

具體說明如下:
Level 0:Web 服務(wù)只是使用 HTTP 作為傳輸方式,實際上只是遠(yuǎn)程方法調(diào)用(RPC)的一種具體形式。SOAP 和 XML-RPC 都屬于此類。
Level 1:Web 服務(wù)引入了資源的概念。每個資源有對應(yīng)的標(biāo)識符和表達(dá)。
Level 2:Web 服務(wù)使用不同的 HTTP 方法來進行不同的操作,并且使用 HTTP 狀態(tài)碼來表示不同的結(jié)果。如 HTTP GET 方法來獲取資源,HTTP DELETE 方法來刪除資源。
Level 3:Web 服務(wù)使用 HATEOAS。在資源的表達(dá)中包含了鏈接信息??蛻舳丝梢愿鶕?jù)鏈接來發(fā)現(xiàn)可以執(zhí)行的動作。例如,客戶端通過訂單資源中包含的鏈接取消某一訂單,GET 請求被發(fā)送去獲取該訂單。HATEOAS 的優(yōu)點包括無需在客戶端代碼中寫入硬鏈接的 URL。此外,由于資源信息中包含可允許操作的鏈接,客戶端無需猜測在資源的當(dāng)前狀態(tài)下執(zhí)行何種操作。
3.4 RESTful
RESTful是一種常見的REST應(yīng)用,是遵循REST風(fēng)格的web服務(wù),REST式的web服務(wù)是一種ROA(面向資源的架構(gòu))。
RESTful資源操作如下表:
| http方法 | 資源操作 | 冪等 | 安全 |
|---|---|---|---|
| GET | SELECT | 是 | 是 |
| POST | INSERT | 否 | 否 |
| PUT | UPDATE | 是 | 否 |
| DELETE | DELETE | 是 | 否 |
注:冪等性:對同一REST接口的多次訪問,得到的資源狀態(tài)是相同的;安全性:對該REST接口訪問,不會使服務(wù)器端資源的狀態(tài)發(fā)生改變。
RESTful URL請求格式與傳統(tǒng)請求格式比較如下表所示:
| 傳統(tǒng)URL請求格式 | RESTFul請求格式 | 描述 |
|---|---|---|
| http:/localhost/user/query/1 GET | http:/localhost/user/1 GET | 根據(jù)用戶id查詢用戶數(shù)據(jù) |
| 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默認(rèn)路由是通過http的方法(get/post/put/delete)去匹配對應(yīng)的action,也就是說webapi的默認(rèn)路由并不需要指定action的名稱,當(dāng)然,WebApi也支持MVC里面的路由機制,但RestFul風(fēng)格的服務(wù)要求請求的url里面不能包含action,所以,在WebApi里面是并不提倡使用MVC路由機制的。下邊通過例子介紹Web API路由原理以及使用。
4.1 新建空的Web API應(yīng)用程序
上篇博客介紹了手動搭建基本框架,這次我們通過VS2017提供的模板,新建空的WebAPI應(yīng)用程序MyWebAPI2,構(gòu)建過程中僅勾選Web API,如下圖所示。

創(chuàng)建完成,應(yīng)用程序的目錄結(jié)構(gòu)如下圖所示。

4.2 默認(rèn)路由
App_Start文件夾下WebApiConfig.cs類用于注冊Web API路由,默認(rèn)路由代碼如下:
public static void Register(HttpConfiguration config)
{
//默認(rèn)路由
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,并修改相關(guān)代碼,如下所示:
public class StudentController : ApiController
{
private static List<Student> studentList = new List<Student>()
{
new Student() {Id = "001", Name = "張三", Sex = "男", Age = 19, Dept = "軟件學(xué)院"},
new Student() {Id = "002", Name = "李麗", Sex = "女", Age = 19, Dept = "資環(huán)學(xué)院"}
};
[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;
}
}
在實際項目中,增刪改查這些操作都是和數(shù)據(jù)庫打交道的,這里為了演示具體實現(xiàn),用一個靜態(tài)數(shù)組存儲數(shù)據(jù)。運行程序,我們采用Fiddler工具進行測試調(diào)用。
測試一:調(diào)用Get()方法
功能說明:通過路由解析StudentController中的Get()Action,獲取所有學(xué)生列表。
URL:http://localhost:52317/api/student;HTTP方法:GET。如下圖所示:

測試結(jié)果:HTTP狀態(tài)碼為200,獲取的JSON數(shù)據(jù)如下所示:

測試二:調(diào)用Get(string id)方法
功能說明:通過路由解析StudentController中的Get(string id)Action,根據(jù)學(xué)號獲取某個學(xué)生信息。
URL:http://localhost:52317/api/student/002;HTTP方法:GET。
測試結(jié)果:HTTP狀態(tài)碼為200,獲取的JSON數(shù)據(jù)如下所示:

測試三:調(diào)用Post([FromBody]Student student)方法
功能說明:通過路由解析StudentController中的Post([FromBody]Student student)Action,插入一條數(shù)據(jù),其中參數(shù)前添加[FromBody]是指該參數(shù)不是從URL中獲取,而是在RequestBody中獲取。
URL:http://localhost:52317/api/student;HTTP方法:POST。在RequestBody輸入Json格式的數(shù)據(jù),如下圖所示:

測試結(jié)果:HTTP狀態(tài)碼為200,返回的數(shù)據(jù)TRUE。重復(fù)測試一,獲取所有學(xué)生列表如下圖所示。

測試四:調(diào)用Put(string id, [FromBody]Student student)方法
功能說明:通過路由解析StudentController中的Put(string id, [FromBody]Student student)Action,實現(xiàn)通過學(xué)號更新信息,其中學(xué)號id是在Url中獲取。
URL:http://localhost:52317/api/student/002;HTTP方法:PUT。
測試結(jié)果:HTTP狀態(tài)碼為200,返回的數(shù)據(jù)TRUE。
測試五:調(diào)用Delete(string id)方法
功能說明:通過路由解析StudentController中的Delete(string id)Action,刪除某學(xué)生信息,其中學(xué)號id是在Url中獲取。
URL:http://localhost:52317/api/student/002;HTTP方法:DELETE。
測試結(jié)果:HTTP狀態(tài)碼為200,返回的數(shù)據(jù)TRUE。
總結(jié):通過上邊測試,我們可以到URL中沒有用到Action,默認(rèn)路由就是通過參數(shù)和HTTP方法匹配Controller中的Action。
4.3 自定義路由
在實際項目中,如果http請求的類型相同,且請求參數(shù)相同(如Get),這個時候按照默認(rèn)路由肯定會出問題,具體出現(xiàn)什么問題,我們在這做個測試:
在StudentController中添加一個Action,用于查詢某個學(xué)院的所有同學(xué),代碼如下:
[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/資環(huán)學(xué)院;HTTP方法:GET。測試結(jié)果發(fā)現(xiàn)返回的結(jié)果null,跟蹤測試代碼發(fā)現(xiàn)該URL調(diào)用的Get(string id)這個Action。
那么問題就來了,怎么才能調(diào)用這個Action呢?其中有兩種方法一種是自定義路由,就像MVC那樣在模板中增加Action,另一種為通過Web API路由特性實現(xiàn)。
在App_Start文件夾下WebApiConfig.cs類中添加ActionAPI路由模板,代碼如下:
public static void Register(HttpConfiguration config)
{
//默認(rèn)路由
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/資環(huán)學(xué)院;HTTP方法:GET。運行結(jié)果如下:

注:若出現(xiàn)中文亂碼,解決途徑請參考Fiddler抓包中文亂碼問題。
由此可知通過自定義路由可以解決該問題,但是不符合RESTful風(fēng)格,所有不提倡使用該方法。
4.4 特性路由
默認(rèn)路由解決不了的訪問,可以通過Web API的路由特性解決。
啟動路由特性:在App_Start文件夾下WebApiConfig.cs類中啟用特性路由,代碼如下:
public static void Register(HttpConfiguration config)
{
//啟用Web API特性路由
config.MapHttpAttributeRoutes();
//默認(rèn)路由
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/軟件學(xué)院;HTTP方法:GET。運行結(jié)果如下:

通過此例,可以看到Web API特性路由非常靈活方便,具體使用要結(jié)合實際項目靈活運用。
5. 總結(jié)
本文在開始部分介紹了HTTP協(xié)議及RESTful架構(gòu)風(fēng)格,讓我們更深入的了解Web API的設(shè)計初衷,又通過實際的例子(文中的代碼及結(jié)果均通過了測試),讓我們掌握了路由配置的各種方法。文中若有不足之處,還望海涵,博文寫作不易希望多多支持,后續(xù)會更新更多內(nèi)容,感興趣的朋友可以加關(guān)注,歡迎留言交流!
掃描添加下方的微信公眾號,獲取更多福利和干貨!也可通公眾號(碼探長)聯(lián)系探長,期待與你相遇!??!
