特性路由 是Web API 2 中提出的一種新的類型的路由,正如其名稱那樣,它是經過特性(Attribute) 來定義路由的,相比以前的基於模式(Convertion Based)的路由,特性路由 可以提供更爲靈活和更多的控制。更好的方式是,靈活的組合使用這兩種方式。正則表達式
在 特性路由 以前 的 基於模式 的路由,咱們須要定義一些包含一些參數化字符串的模板,例如,api/{congroller}/{action}/{id}
,當接受到請求後,會將請求的 URI 與這些模板進行匹配,這種方式有一個優勢那就是全部的路由定義均可以在同一個地方進行配置,這些規則將被應用到全部的 Controller,這也形成也其對一些特定的 URI 的匹配不夠靈活,例如,請求的一些資源(Resource) 包含一些子資源,例如顧客具備訂單,電影包含演員,書籍具備做者等,這時天然而然的會建立以下的 URI 來映射這種關係:/customers/1/orders
。此時若是使用 基於模式 的方式來定義路由規則便會極爲的困難,即便可以處理,在具備較多 Controller 和 資源類型時,也並不能達到很好的效果。
使用特性路由 就能夠很好的解決這樣的問題,像上面的例子,使用 特性路由 能夠很方便的定義知足條件的路由規則,以下所示:express
[Route("customers/{customerId}/orders")] public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
爲了啓用特性路由,須要調用 System.Web.Http.HttpConfigurationExtensions 的擴展方法 MapHttpAttributeRoutes
進行配置。在 App_Start
目錄的 WebApiConfig
類中進行配置。c#
public static void Register(HttpConfiguration config) { // Web API 路由 config.MapHttpAttributeRoutes(); //Convention-based Route config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
下面有一些使用 特性路由 的技巧api
api/v1/products
api/v2/products
/// <summary> /// 根據商品名稱獲取商品 /// </summary> /// <param name="name"></param> /// <returns></returns> [Route("product/{name}")] public IHttpActionResult GetProduct(string name) { if (string.IsNullOrEmpty(name)) return Ok(products); var product = products.Where(p => { if (p.Name == name) return true; return false; }).FirstOrDefault(); if(product != null){ return Ok(product); } else { return NotFound(); } } /// <summary> /// 返回商品列表 /// </summary> /// <returns></returns> [Route("product")] public IHttpActionResult GetProduct() { return Ok(products); }
/// <summary> /// 根據商品Id獲取商品信息 /// </summary> /// <param name="id"></param> /// <returns></returns> [Route("api/product/{id:int}")] public IHttpActionResult GetProduct(int id) { } /// <summary> /// 根據上市時間獲取商品 /// </summary> /// <param name="marketdate">上市時間</param> /// <returns></returns> [Route("api/product/{*marketdate:datetime}")] public IHttpActionResult GetProduct(DateTime marketdate) { }
仔細觀察,上面配置的特性路由都具備相同的前綴 api/product/
, 使用路由前綴(Route Prefix)咱們能夠在 Controller 進行統一設置,例如前面的例子,咱們就能夠改成下面的樣子:ide
[RoutePrefix("api/product")] public class DefaultController : ApiController { [Route("{id:int}")] public IHttpActionResult GetProduct(int id){ } }
咱們還可使用 ~
符號對使用了路由前綴的 Controller 中的方法進行路由重寫,例如:ui
[RoutePrefix("api/product")] public class DefaultController : ApiController { [Route("~/api/discount/product/{id:int}")] public IHttpActionResult GetProduct(int id){ }
上面的例子使用 ~
對路由進行了重寫,新的路由規則將覆蓋 Controller 定義的路由前綴,如今該方法所匹配的規則爲 api/discount/product/{id:int}
,而非原來的 api/product/{id:int}
。
在定義路由前綴時,還能夠包含一些參數,例如:code
[RoutePrefix(api/{customerid})] public class DefaultController : ApiController{ [Route("order")] public IEnumerable<Order> GetOrders(int customerid){ } }
咱們可使用 ?
將一個路由參數標記爲 可選參數(optional parameter),若是一個路由參數被標記爲可選的,則必須爲其設置默認值。排序
[Route("api/product/{id:int?}")] public IHttpActionResult GetProduct(int id =1){ }
此時,對於 /api/product/id/1
和 /api/product
將返回相同的結果。
咱們不只能夠在方法中設置參數的默認值,還能夠在路由特性中進行設置。以下所示,接口
[Route("api/product/{id:int=1}")] public IHttpActionResult GetProduct(int id){ }
上面設置默認值的方法基本徹底相同,可是在應用該值時仍是有細微的區別,以下所示:
ci
1
是直接分配給方法的參數的,所以其老是具備肯定的值"1"
被轉換爲 System.Int32 類型的 1,然而,咱們能夠定義本身的模型綁定器,所以,最終參數 id 的默認值並非肯定的,由於其在自定義的模型綁定器中可能被轉換爲其它的結果。一般狀況下,使用兩種形式給予參數默認值的形式的結果是相同的。
經過路由參數約束咱們能夠將路由模板中的參數限制爲指定的類型,通用的語法以下所示:{parameter:costraint}
,例如前面的例子,將 id
的類型限制爲 System.Int32
[Route("api/product/{id:int}")] public IHttpActionResult GetProduct(int id){} [Route("api/product/name")] public IHttpActionResult GetProduct(string name){}
只有請求的參數爲 Int 類型時纔會匹配第一個路由規則,不然匹配第二個。
下表羅列了可用的約束,
約束 | 描述 | 例子 |
---|---|---|
alpha | 將參數約束爲大寫或者小寫的拉丁字母(a-z,A-Z) | {x:alpha} |
bool | 將參數限制爲 bool 類型 | {x:bool} |
datetime | 將參數限制爲 date | {x:date} |
decimal | 將參數限制爲 decimal 類型 | {x:decimal} |
float | 將參數限制爲 32 位浮點數類型 | {x:float} |
double | 將參數限制爲 64 位浮點數類型 | {x:double} |
int | 將參數限制爲 32 整形 | {x:int} |
long | 將參數限制爲 64位整形 | {x:long} |
guid | 將參數類型限制爲 guid 類型 | {x:guid} |
length | 將參數的長度限制爲指定長度或指定範圍的長度 | {x:length(5)/{x:length(1,10)} |
min/max | 限制參數(整形)最大或最小值 | {x:min(10)}/{x:max(10)} |
minlength/maxlength | 限制參數的最小長度或最大長度 | {x:minlength(1)}/{x:maxlength} |
range | 限制參數(整形) 的範圍,包含兩端 | {x:range(1,3)} |
regex | 限制參數必須匹配指定的正則表達式 | {x:regex(\expression)} |
能夠同時使用多個約束條件,每一個條件之間經過 :
進行分割,例如
[Route("api/product/{id:int:min(1)}")] public IHttpActionResult GetProduct(int id){}
除了 內置的約束條件,咱們還能夠定製本身的約束條件,方法是實現 IHttpConstraint
接口,重寫其 public bool Match( HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection )
方法
而後經過以下的方式註冊自定義的約束後,即可像使用內置約束那樣的使用自定義約束了
public static class WebApiConfig { public static void Register(HttpConfiguration config) { var constraintResolver = new DefaultInlineConstraintResolver(); constraintResolver.ConstraintMap.Add(identity, typeof(CustomConstrainType)); config.MapHttpAttributeRoutes(constraintResolver); } }
Web API 在選擇 Action 時還會基於請求的 Http 方法進行判斷, Web Api 默認會匹配控制器方法的開端(匹配不區分大小寫),例如,GetProduct
便會被識別爲一個 Http Get
方法。咱們可使用內置的一些 Http Method 特性來覆蓋默認的實現。
下面的方法,使用 HttpPost 特性將其標記爲 POST 方法
[HttpPost] [Route("api/product/{id}")] public IHttpActionResult CreateProduct(){}
除此以外,還可使用AcceptVerbs
特性來實現,其接受一個上述特性的列表(方法名字符串列表)
在 Web Api 中每一個路由都有本身的名稱,這個名稱在產生連接是十分的有用。
[Route("{id:int}",Name ="GetProductById")] public Product Get(int id) { var product = products.Where(p => { if (p.Id == id) return true; return false; }).FirstOrDefault(); return product; } [Route("~/api/products/{id:int}")] [HttpGet] public HttpResponseMessage Find(int id) { var response = Request.CreateResponse(HttpStatusCode.Created); string uri = Url.Link("GetProductById", new { id = });///api/product/id //response.Headers.Location = new Uri(uri); response.Content = new StringContent(uri); return response; }
當使用一個 Route 去匹配一個 URI 時,會以一個特定的順序去分析路由,咱們能夠設置 Route 的 RouteOrder 來指定一個路由的順序,RouteOrder 是一個整形數字,默認值爲0,其值越小,順序越靠前。
當接受到一個請求後,會以以下的順序去匹配路由
[RoutePrefix("orders")] public class OrdersController : ApiController { [Route("{id:int}")] // 具備約束的參數 public HttpResponseMessage Get(int id) { ... } [Route("details")] // 字面值 public HttpResponseMessage GetDetails() { ... } [Route("pending", RouteOrder = 1)] public HttpResponseMessage GetPending() { ... } [Route("{customerName}")] // 無約束條件的參數 public HttpResponseMessage GetByCustomer(string customerName) { ... } [Route("{*date:datetime}")] // 通配符 public HttpResponseMessage Get(DateTime date) { ... }
}
按照上面的規則,能夠得出下面的順序:
orders/details
order/{id:int}
order/{customerName}
order/{*date:datetime}
order/penddig