Web API (四) 特性路由(Attribute Route)

特性路由 是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

  1. 指定 API 版本,下面的兩個路由規則分別匹配不一樣版本的 API 方法
  • api/v1/products
  • api/v2/products
  1. 重載 URI 片斷
/// <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);
        }
  1. 使用多種類型的參數
/// <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 是直接分配給方法的參數的,所以其老是具備肯定的值
  • 對於第二種方式,默認值經過了 模型綁定(Model- Binding) 的過程,在綁定過程當中,它會從 "1" 被轉換爲 System.Int32 類型的 1,然而,咱們能夠定義本身的模型綁定器,所以,最終參數 id 的默認值並非肯定的,由於其在自定義的模型綁定器中可能被轉換爲其它的結果。

  一般狀況下,使用兩種形式給予參數默認值的形式的結果是相同的。

路由參數約束(Constraint)

  經過路由參數約束咱們能夠將路由模板中的參數限制爲指定的類型,通用的語法以下所示:{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);
    }
}
Http 方法

  Web API 在選擇 Action 時還會基於請求的 Http 方法進行判斷, Web Api 默認會匹配控制器方法的開端(匹配不區分大小寫),例如,GetProduct 便會被識別爲一個 Http Get 方法。咱們可使用內置的一些 Http Method 特性來覆蓋默認的實現。

  • HttGet
  • HttpPost
  • HttpHead
  • HttpOptions
  • HttpDelete
  • HttpPacth
  • HttpPut

下面的方法,使用 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,其值越小,順序越靠前。
  當接受到一個請求後,會以以下的順序去匹配路由

  1. 比較路由表中各路由的 RouteOrder
  2. 查看各路由模板中的 URI 片斷,對於每一個片斷按照下面的順序排列
    1. 字面值(Literal )片斷
    2. 具備約束條件的路由參數
    3. 沒有約束條件的路由參數
    4. 具備約束條件的通配符參數片斷
    5. 沒有約束條件的通配符參數片斷
  3. 最後,這些路由會按照不區分大小寫、獨立於語言的排序方式進行排序
      總的來講,就是條件越具體的匹配時的順序越靠前。
[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) { ... }

}

 按照上面的規則,能夠得出下面的順序:

  1. orders/details
  2. order/{id:int}
  3. order/{customerName}
  4. order/{*date:datetime}
  5. order/penddig
相關文章
相關標籤/搜索