介紹
當列出REST API的最佳實踐時,Routing(路由)總是使它位於堆棧的頂部。今天,在這篇文章中,我們將使用特定於.NET Core的REST(web)API來處理路由概念。
對於新手API開發人員,技術顧問以及與REST API相關的所有其他IT專業人員(尤其是使用Microsoft技術堆棧),本文將解釋使用Microsoft .NET Core在REST API中重點關注屬性路由的重要性和功能。
概論
路由是開發者社區中眾多公開爭論的話題,這是一個有趣的功能,可以深入研究。路由是API使用的基於功能的標記或Uri模板,以匹配期望執行的所需操作或方法。在開發過程中有兩種類型或兩種不同類型的路由。也就是說,'Convention-Based Routing'是REST路由家族中的長子,之后是'Attribute Routing',是迄今為止最可愛的兒子。如前所述,在API開發和設計階段,使用哪種類型的路由機制是一個值得討論的話題。
在“基於約定的路由”中,路由模板是由開發人員根據需要定義的,基本上是一組帶有參數的文本類型的字符串。收到請求后,它會嘗試將請求的URI與此定義的路由模板進行匹配。使用這種路由類型的唯一好處是,模板是在應用程序解決方案結構中的單個位置定義的,在控制器和操作中虔誠地利用模板規則。
那么,為什么屬性路由很重要?是的,這不僅是重要的,而且強烈建議社區開發人員和架構師開發API。雖然基於約定的路由有其自己的優點,但是在構建一個好的API的時候,很少考慮這種類型的路由是不可取的。REST API中有通用的URI模式,這很難通過基於約定的路由來支持。考慮一組響應數據或資源,往往被分層數據或子資源夾雜着。例如。部門有員工,歌曲有歌手,電影有演員等等。在這種情況下預期的URI是,
/電影/ 1 /演員
在多個控制器和巨大資源的情況下,這種類型的URI雖然可以使用基於約定的路由來實現,但是以縮放和性能為代價是困難的。這是設計可擴展API的關鍵考慮因素。這里是另一個路由類型,屬性路由,扮演一個角色。
屬性路由
什么是屬性路由?
從技術上說,屬性路由就是將路由作為一個屬性附加到特定的控制器或操作方法。裝飾控制器及其使用[Route]屬性定義路由的方法稱為屬性路由。簡單來說,在控制器和方法中使用[Route]屬性是Attribute Routing。
[Route(“api / customers / {id} / orders”)]
它從Web API 2開始,現在是RESTful APIs設計和開發中最推薦和改編的路由類型。
為什么使用屬性路由?
如名稱所示,屬性路由使用屬性來定義路由。通過屬性路由,您可以精確地控制URI,而不是基於約定的路由。以上所述的分層資源場景可以通過屬性路由輕松實現,不會對API的可擴展性造成影響。
另外,版本控制API,重載URI段和多參數類型模式可以通過屬性路由輕松實現。
使用屬性路由
控制器上的任何路由屬性都會使控制器中的所有操作屬性路由。為動作定義路由屬性或控制器優先於傳統路由。讓我們更精確的.NET Core API,它默認帶有屬性路由。屬性路由需要詳細的輸入來指定路由。但是,它允許更多地控制哪個路由模板適用於每個操作。
配置
當您使用.NET Core框架創建WEB API時,您可以在其Startup.cs文件中注意到,
-
void Configure(IApplicationBuilder app,IHostingEnvironment env,ILoggerFactory loggerFactory){
-
app.UseMvc();
-
}
在配置部分聲明'app.UseMvc()',可以啟用屬性路由。這是.NET Core應用程序的默認配置。因此,啟用.NET Core Web API的屬性路由不需要顯式配置。
命名空間下面用於裝飾[Route]作為屬性。
使用Microsoft.AspNetCore.Mvc;
與Web API在MVC路由上的核心區別在於,它使用HTTP方法而不是URI路徑來選擇操作。屬性路由還使用HTTP動作動詞來根據請求定義對控制器下方法的操作。
[HttpGet],[HttpPost],HttpPut(“{id}”)],[HttpDelete(“{id}”)]和所有其他記錄的動作動詞。
在.NET Core項目中,默認情況下,控制器使用指定相應的HTTP動作動詞的CRUD方法進行修飾。
下面是由.NET Core創建的默認控制器,

-
using System;
-
using System.Collections.Generic;
-
using System.Linq;
-
using System.Threading.Tasks;
-
using Microsoft.AspNetCore.Mvc;
-
namespace WebAPIwiithCore.Controllers
-
{
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller
-
{
-
//獲取api /值
-
[HTTPGET]
-
publicIEnumerable <string> Get()
-
{
-
returnnewstring [] { “value1” , “value2” };
-
}
-
//獲取api / values / 5
-
[HttpGet(“{id}” )]
-
public String get(int id)
-
{
-
返回“價值” ;
-
}
-
// POST api / values
-
[HttpPost]
-
publicvoid Post([FromBody] string Value)
-
{
-
}
-
// PUT api / values / 5
-
[HttpPut(“{id}” )]
-
publicvoid Put(int id,[FromBody] string value)
-
{
-
}
-
//刪除api / values / 5
-
[HttpDelete(“{id}” )]
-
publicvoid刪除(int id)
-
{
-
}
-
}
-
}
如果您注意到,控制器類“ValuesController”是由路由裝飾的,
〔Route( “API / [Controller]”)]
該功能是在稱為路由令牌的.NET核心框架中引入的。令牌[controller]從定義路由的動作或類中替換控制器名稱的值。
這里的控制器名稱是由路由控制器令牌裝飾的值,
-
[Route(“API / [Controller]” )]
-
publicclassValuesController:Controller {...} //匹配'/ api / Values'
現在讓我們將控制器名稱更改為MoviesController;
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {...} //現在匹配'/ api / Movies'
在這里,URI'api / values'正在工作得更好,現在它會拋出一個錯誤(404找不到)。使用“api / Movies”更改URI將提供所需的響應。
對於[行動]和[區域],各自的行動方式和領域也是如此。
現在,讓我們考慮下面的例子,
-
[Route(“API / [Controller” )]
-
publicclassMoviesController:Controller {
-
[HTTPGET]
-
publicIEnumerable <string> Get(){
-
returnnewstring [] {
-
“value1” ,
-
“value2”
-
};
-
}
-
[HttpPost]
-
publicvoid Post([FromBody] string value){
-
return;
-
}
-
}
對於上面給出的代碼片段,
URI,
-
// Get / api / Movies /將匹配方法1。
-
// Post / api / Movies /將匹配方法2。
請注意,這兩個URI是相同的,唯一的區別在於它是如何被Get或Post方法調用的。這使Web API路由不同於MVC路由。
模式
讓我們看一下其他幾個例子,如上所述,通過屬性路由來緩解。
API版本控制
在本例中,“Get / api / movies / v1 /”將被路由到方法1,“Get / api / movies / v2 /”將被路由到方法2。

-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {
-
[HttpGet(“v1” )]
-
publicIEnumerable <string> Get(){
-
returnnewstring [] {
-
“V1.value1” ,
-
“V1.value2”
-
};
-
}
-
[HttpGet(“v2” )]
-
publicIEnumerable <string> Get(){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
-
}
請注意,版本控制主要由不同的控制器處理。這里為了便於理解,我們傾向於用不同的方法用相同的簽名來描述它。
示例清楚地表明,屬性路由是如何處理復雜情況的最簡單方法。
重載的URI
在這個例子中,“id”是一個可以作為數字傳遞的參數,但是“knownited”映射到一個集合。
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {
-
[HttpGet(“{id}” )]
-
publicIEnumerable <string> Get(int id){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
-
[HttpGet(“get” )]
-
publicIEnumerable <string> Get(){
-
returnCollections ..
-
}
-
}
URI,
-
// Get / api / Movies / 123將匹配方法1。
-
// Get / api / Movies / notedited將匹配方法2。
多個參數類型
在這個例子中,“id”是一個參數,可以作為數字或字母表傳遞任何空閑的字符串。
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {
-
[HttpGet(“{id:int}” )]
-
publicIEnumerable <string> Get(int id){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
-
[HttpGet(“id:aplha” )]
-
publicIEnumerable <string> Get(string id){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
-
}
URI,
-
// Get / api / Movies / 123將匹配方法1。
-
// Get / api / Movies / abc將匹配方法2。
上面的例子有一些需要注意的地方,
HTTPGET( “{ID:int”)]
提到參數數據類型被接受,被稱為約束。我們將在后面的文章中通過這個。
多個參數
在這個例子中,'id'和'authorid'是一個可以作為數字傳遞的參數。
-
[Route(“API / [Controller]” )]
-
publicclassBooksController:Controller {
-
[HttpGet(“{id:int} / author / {authorid:int}” )]
-
publicIEnumerable <string> Getdetails(int id,intauthorid){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
-
}
匹配URI:// Get api / books / 1 / author / 5在給定的方法中,where 1與'id'和'5'匹配authorid。
多個路線
屬性路由允許我們為相同的控制器和動作或方法定義多個路由。讓我們用例子來理解它,
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {
-
[HttpPut(“Post” )]
-
[HttpPost(“Checkout” )]
-
publicMovieordermovie(){
-
return SomeValue...
-
}
-
}
如示例中所示,Method'Ordermovie'返回模型類“Movie”的某個值。該方法定義了兩個路由。一個帶有HTTP動詞Put,主要用於CRUD中的更新操作,另一個帶有HTTP動詞Post,用於創建或添加數據。兩者都指的是相同的方法。
在這種情況下,下面的URI將匹配路由,
URI 1匹配// PUT api / movies /購買
URI 2匹配//發布api /電影/結帳
備注
路線1和路線2用於更好的理解目的,與訂購無關。
我們甚至可以在控制器上定義多個路由。在這種情況下,來自控制器的兩條路線都與兩條路線相結合。看下面的例子,
-
[Route(“api / Store” )]
-
[Route(“API / [Controller]” )]
-
publicclassMoviesController:Controller {
-
[HttpPut(“post )]
-
[HttpPost(“Checkout” )]
-
publicMovieordermovie(){
-
returnSomeValue..
-
}
-
}
在這種情況下,下面的URI將匹配路由,
URI 1匹配// PUT api / movies / buy&api / store / buy
URI 2匹配//發布api /電影/結帳&api / store / checkout
路線約束
路由約束提供對路由中使用的匹配參數的控制。在路由中定義參數的語法是“{parameter:constraint}”。
例
-
[HttpGet(“api / constraint / {id:int}” )]
-
publicIEnumerable <string> Get(int id){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
匹配路由:api / constraint / 1
只有整數值將被路由到“有效”資源的“Get”方法。任何其他非整數值都不會受到方法的影響,比如api / constraint / abc將不會被路由。
在這里,可能有一個問題。如果客戶端調用,api / constraint / 0仍然會被路由到Get方法,這是錯誤的。所以為了遏制這個問題,我們可以為參數接受大於零的值添加另一個約束。這可以通過添加約束'min(1)'來實現,其中1是由“min”約束接受的參數。()“括號中可以接受參數
我們可以通過用冒號分隔約束來在單個參數上添加多個約束。
語法
“{參數:約束:約束}”。
例
-
[HttpGet(“api / constraint / {id:int:min(1)}” )]
-
publicIEnumerable <string> Get(int id){
-
returnnewstring [] {
-
“V2.value1” ,
-
“V2.value2”
-
};
-
}
的URI
-
api / constraint / 1||> 1將匹配。
-
api / constraint / 0不匹配。
-
api / constraint / -1或負數不匹配。
類似的約束,比如字符串的'alpha',布爾值的'bool',日期時間的'datetime'值,'min','max','range'和特定范圍的值等。
屬性路由功能雖然是API設計棧中最推薦的實現。從API的可擴展性到API讓客戶端可讀,屬性路由在API開發中扮演着重要的角色。Microsoft在其.NET Core框架中啟用默認路由作為屬性路由,關閉了使用路由的所有可能的線程差異。

