Web API-屬性路由

        路由(Routing)就是Web API如何將一個URI匹配到一個action的過程。Web API 2 支持一個新的路由方式-屬性路由(attribute routing)。顧名思義,屬性路由使用標識屬性去定義路由,屬性路由可使你在Web API中更方便的定製你的URIs。例如,你能夠很容易的建立描述不一樣層次資源的URIs。express

        前面將的路由風格,叫作基於約定的路由(convention-based)在Web API中也徹底支持,實際上,你可以在項目中同時使用兩種路由技術。api

        這篇文章主要演示如何在項目中啓用屬性路由(attribute routing)和描述各類屬性路由的使用方式,主要內容:框架

    --一、爲何須要屬性路由ui

    --二、容許屬性路由spa

    --三、添加路由屬性版本控制

    --四、路由前綴code

    --五、路由約束blog

    --六、可選擇的URI參數以及默認值排序

    --七、路由名稱接口

    --八、路由順序

一、爲何須要屬性路由

        第一個發佈版的Web API使用 convention-based routing(基於約定的)。在這種風格的路由中,你定義一個或者多個路由模版,基本上是參數化的字符串。當框架接收到一個請求,框架將URI與路由模版進行匹配。

        convention-based路由的一個優點是:路由模版定義在一個文件中,路由規則被應用到因此的控制器上。可是convention-based方式的路由風格,要實現支持像RESTful APIs中常見的特定的URI模式比較麻煩。例如,資源常常包含子資源:Customers have orders, movies have actors, books have authors,等等。建立可以反映這種關係的URI是必須的:/customers/1/orders

使用convention-based 路由很難去實現這種風格的URI(This type of URI is difficult to create using convention-based routing),儘管能夠實現,但結果不佳,若是你有不少控制器和資源類型。

        使用屬性路由,你能夠很容易的爲這樣的URI定義一個路由,只須要給一個控制器的action添加一個標識屬性:

[Route("customers/{customerId}/orders")]
public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }

還有一些狀況下使用屬性路由將很是方便:

--API 版本(API versioning)

/api/v1/products
/api/v2/products

解釋:假設要控制請求訪問不一樣版本的api,若是是convention-based風格的路由,意味着這裏的v1 ,v2被定義爲參數,那麼必須在action中接收這個參數,而後在action中才能判斷出版本(貌似這個時候知道版本用處不大了)咱們要實現的是v1 ,v2 訪問的是不一樣的控制器。那麼使用屬性路由很容易實現這一點,好比一個方法只在v2中有:

[Route("/api/v2/products")]
public Ienumerable<Product> GetAll(){}

(如何實現版本控制,細節可能要在項目中去感覺)

--重載 URI片斷(Overloaded URI segments)

/orders/1
/orders/pending

這個例子中"1"是一個訂單編號,可是"pending"對應了一個訂單集合。

--複雜的參數類型

/orders/1
/orders/2013/06/16

這個例子中"1" 是一個訂單編號,可是"2013/06/16"指定了一個日期。

二、容許屬性路由

Global.asax文件

protected void Application_Start()
{
    // Pass a delegate to the Configure method.
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

WebApiConfig類

public static void Register(HttpConfiguration config)
{
     // Web API routes
     config.MapHttpAttributeRoutes();

     // Other Web API configuration not shown.
}

config.MapHttpAttributeRoutes()方法啓用屬性路由。

三、添加路由標識屬性

一個例子:

public class OrdersController : ApiController
{
    [Route("customers/{customerId}/orders")]
    [HttpGet]
    public IEnumerable&lt;Order&gt; FindOrdersByCustomer(int customerId) { ... }
}

字符串"customers/{customerId}/orders"是路由的URI模版,Web API嘗試將請求URI與這個模版匹配,這個例子中"coustomers" 和 "orders" 是純文本片斷,{customerId}是佔位符,下面的URI都會與這個模版匹配:

  • http://localhost/customers/1/orders
  • http://localhost/customers/bob/orders
  • http://localhost/customers/1234-5678/orders

可使用約束限制{customerId}匹配的範圍,下面會講到。

注意在路由模版中的{customerId}參數,和action方法中的customerId參數匹配,當Web API調用控制器的action,將進行參數綁定,例如若是URI是: http://example.com/customers/1/orders  Web API會將值「1」傳遞的action方法的customerId參數。一個URI模版能夠有多個佔位符參數:

[Route("customers/{customerId}/orders/{orderId}")]
public Order GetOrderByCustomer(int customerId, int orderId) { ... }

--HTTP請求方式

默認狀況下Action方法使用方法開頭用請求方式名稱的方式兩匹配不一樣的HTTP請求(忽略大小寫的),能夠經過添加標識屬性來指定某一個action方法匹配的HTTP請求方式:

[HttpDelete]

[HttpGet]

[HttpHead]

[HttpOptions]

[HttpPatch]

[HttpPost]

[HttpPut]

例如:

[Route("api/books")]
[HttpPost]
public HttpResponseMessage CreateBook(Book book) { ... }// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }
 

四、路由前綴

        不少時候一個控制器下的action路由模版的前面部分都是相同的,爲了避免重複書寫,能夠這樣:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET api/books
    [Route("")]
    public IEnumerable&lt;Book&gt; Get() { ... }

    // GET api/books/5
    [Route("{id:int}")]
    public Book Get(int id) { ... }

    // POST api/books
    [Route("")]
    public HttpResponseMessage Post(Book book) { ... }
}

[RoutePrefix("api/books")] 給控制器下的全部action方法增長了路由模版前綴。

若是有特殊狀況,你能夠在action方法的標識屬性中使用浪符號(~)來覆蓋指定的統一的路由前綴:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET /api/authors/1/books
    [Route("~/api/authors/{authorId:int}/books")]
    public IEnumerable<Book> GetByAuthor(int authorId) { ... }

    // ...
}

路由前綴也能夠包含佔位符參數:

[RoutePrefix("customers/{customerId}")]
public class OrdersController : ApiController
{
    // GET customers/1/orders
    [Route("orders")]
    public IEnumerable<Order> Get(int customerId) { ... }
}

五、路由約束

路由約束容許你限制路由模版中佔位符參數的匹配範圍,基本語法是{parameter:constraint}。例如:

[Route("users/{id:int}"]
public User GetUserById(int id) { ... }

[Route("users/{name}"]
public User GetUserByName(string name) { ... }

GetUserById 方法只匹配id參數爲整型的URI

支持的約束條件:

Constraint

Description

Example

alpha

Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z)

{x:alpha}

bool

Matches a Boolean value.

{x:bool}

datetime

Matches a DateTime value.

{x:datetime}

decimal

Matches a decimal value.

{x:decimal}

double

Matches a 64-bit floating-point value.

{x:double}

float

Matches a 32-bit floating-point value.

{x:float}

guid

Matches a GUID value.

{x:guid}

int

Matches a 32-bit integer value.

{x:int}

length

Matches a string with the specified length or within a specified range of lengths.

{x:length(6)}
{x:length(1,20)}

long

Matches a 64-bit integer value.

{x:long}

max

Matches an integer with a maximum value.

{x:max(10)}

maxlength

Matches a string with a maximum length.

{x:maxlength(10)}

min

Matches an integer with a minimum value.

{x:min(10)}

minlength

Matches a string with a minimum length.

{x:minlength(10)}

range

Matches an integer within a range of values.

{x:range(10,50)}

regex

Matches a regular expression.

{x:regex(^\d{3}-\d{3}-\d{4}$)}

有些約束條件能夠組合使用,好比"min":必須爲整型且大於或等於1

[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { ... }

自定義路由約束

經過實現IHttpRouteConstraint接口來建立自定義的路由約束,例以下面的代碼定義了一個「不能爲0」的整數約束。

public class NonZeroConstraint : IHttpRouteConstraint
{
    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, 
        IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            long longValue;
            if (value is long)
            {
                longValue = (long)value;
                return longValue != 0;
            }

            string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
            if (Int64.TryParse(valueString, NumberStyles.Integer, 
                CultureInfo.InvariantCulture, out longValue))
            {
                return longValue != 0;
            }
        }
        return false;
    }
}

下面代碼展現若是註冊自定義的約束:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var constraintResolver = new DefaultInlineConstraintResolver();
        constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));

        config.MapHttpAttributeRoutes(constraintResolver);
    }
}

如今就能夠在路由中使用這個自定義的約束條件了:id必須爲非0整數

[Route("{id:nonzero}")]
public HttpResponseMessage GetNonZero(int id) { ... }

還能夠經過實現IInlineConstraintResolver 接口的方式來覆蓋全部的內置的約束。

六、可選擇的URI參數和默認值

你能夠經過給路由參數添加 問號"?」的方式來標識這個參數是可選的,若是路由模版中定義了可選參數,那麼必須爲action方法參數指定一個默認值(可選參數)。

public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int?}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}

上面這個例子,

/api/books/locale/1033 和 /api/books/locale 將返回一樣的資源

還能夠在路由模版中直接指定默認值:

public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int=1033}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
}

上面兩個例子功能基本相同,但也有細微的差異:

--在第一個例子中「{lcid:int?}」,默認值直接在action方法的參數位置指定,因此action方法有一個肯定類型的默認值。

--第二個例子中「{lcid=1033}」,由於在路由模版中指定的默認值,那麼須要通過模型綁定的過程,模型綁定過程會將「1033」從字符類型轉換成數字類型,如何自定義了模型綁定方式,可能還有其餘的不一樣的地方。

兩個例子的功能通常狀況下都是相同的,除非自定義了模型綁定。

七、路由名稱

在Web API中每個路由項都有一個名稱,路由名稱在生成連接的時候很是有用,隱藏你能夠在返回消息中包含一個有效的連接。

使用Name 屬性指定路由名稱,下面的例子展現瞭如何定義路由名稱,以及如何使用路由名稱生成連接:

public class BooksController : ApiController
{
    [Route("api/books/{id}", Name="GetBookById")]
    public BookDto GetBook(int id) 
    {
        // Implementation not shown...
    }

    [Route("api/books")]
    public HttpResponseMessage Post(Book book)
    {
        // Validate and add book to database (not shown)

        var response = Request.CreateResponse(HttpStatusCode.Created);

        // Generate a link to the new book and set the Location header in the response.
        string uri = Url.Link("GetBookById", new { id = book.BookId });
        response.Headers.Location = new Uri(uri);
        return response;
    }
}

八、路由順序

當框架嘗試去將一個URI匹配到一個路由時,會給路由進行排序,若是須要自定義順序,能夠在路由標識屬性中使用RouteOrder 屬性,較小的值排在前面,默認的排序值是0。

排序是如何肯定的:

1.比較路由標識屬性的RouteOrder屬性值。

2.查看路由模版中的每個URI片斷,對於每個片斷,按照下面的方式排序

    1-純文本片斷

    2-帶約束條件的路由參數

    3-不帶約束條件的路由參數

    4-帶約束條件的通配符路由參數

    5不帶約束條件的通配符路由參數

3.In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.

看例子:

[RoutePrefix("orders")]
public class OrdersController : ApiController
{
    [Route("{id:int}")] // constrained parameter
    public HttpResponseMessage Get(int id) { ... }

    [Route("details")]  // literal
    public HttpResponseMessage GetDetails() { ... }

    [Route("pending", RouteOrder = 1)]
    public HttpResponseMessage GetPending() { ... }

    [Route("{customerName}")]  // unconstrained parameter
    public HttpResponseMessage GetByCustomer(string customerName) { ... }

    [Route("{*date:datetime}")]  // wildcard
    public HttpResponseMessage Get(DateTime date) { ... }
}

那麼這些路由的排序以下:

一、orders/details

二、orders/{id}

三、orders/{customerName}

四、orders/{*date}

五、orders/pending

前面有講過,在URI匹配路由模版時是從路由的排列順序開始匹配,一旦匹配成功則會忽略後面的路由模版了。

相關文章
相關標籤/搜索