ASP.NET Web API 2 中的屬性路由Attribute Routing in ASP.NET Web API 2

藉由Mike Wassonby Mike Wasson

路由是 Web API 如何比對動作的 URI。Routing is how Web API matches a URI to an action. Web API 2 支援新類型的路由,稱為屬性路由Web API 2 supports a new type of routing, called attribute routing. 如同名稱所暗示,屬性路由會使用屬性來定義的路由。As the name implies, attribute routing uses attributes to define routes. 屬性路由可讓您更充分掌控 Uri 在 web API 中。Attribute routing gives you more control over the URIs in your web API. 例如,您可以輕鬆地建立描述階層的資源的 Uri。For example, you can easily create URIs that describe hierarchies of resources.

呼叫以慣例為基礎的舊版樣式的路由,路由仍然完整支援。The earlier style of routing, called convention-based routing, is still fully supported. 事實上,您可以結合這兩種技巧,在相同的專案。In fact, you can combine both techniques in the same project.

本主題說明如何啟用屬性路由,並且描述屬性路由的各種選項。This topic shows how to enable attribute routing and describes the various options for attribute routing. 使用屬性路由端對端教學課程中,請參閱使用 Web API 2 中的屬性路由建立 REST APIFor an end-to-end tutorial that uses attribute routing, see Create a REST API with Attribute Routing in Web API 2.

必要條件Prerequisites

Visual Studio 2017 Community、 Professional 或 Enterprise editionVisual Studio 2017 Community, Professional, or Enterprise edition

或者,使用 NuGet 套件管理員來安裝必要的套件。Alternatively, use NuGet Package Manager to install the necessary packages. 工具功能表,在 Visual Studio 中,選取NuGet 套件管理員,然後選取Package Manager ConsoleFrom the Tools menu in Visual Studio, select NuGet Package Manager, then select Package Manager Console. 在 [套件管理員主控台] 視窗中輸入下列命令:Enter the following command in the Package Manager Console window:

Install-Package Microsoft.AspNet.WebApi.WebHost

為什麼屬性路由?Why Attribute Routing?

使用 Web API 的第一版以慣例為基礎路由。The first release of Web API used convention-based routing. 在這類的路由中,您定義一個或多個路由範本,基本上便是參數化字串。In that type of routing, you define one or more route templates, which are basically parameterized strings. 當架構收到要求時,它會比對路由範本的 URI。When the framework receives a request, it matches the URI against the route template. (如需以慣例為基礎的路由的詳細資訊,請參閱ASP.NET Web API 中的路由(For more information about convention-based routing, see Routing in ASP.NET Web API.

以慣例為基礎的路由的優點之一是,範本會定義在單一位置,和路由規則會一致地套用到所有控制站。One advantage of convention-based routing is that templates are defined in a single place, and the routing rules are applied consistently across all controllers. 不幸的是,將慣例為基礎的路由,讓更難以支援特定常見於 RESTful Api 的 URI 模式。Unfortunately, convention-based routing makes it hard to support certain URI patterns that are common in RESTful APIs. 例如,資源通常會包含子資源:客戶具有訂單、 電影有動作項目、 書籍有作者,等等。For example, resources often contain child resources: Customers have orders, movies have actors, books have authors, and so forth. 很自然地建立反映這些關聯性的 Uri:It's natural to create URIs that reflect these relations:

/customers/1/orders

此類型的 URI 很難建立使用以慣例為基礎的路由。This type of URI is difficult to create using convention-based routing. 雖然也可以完成,但如果您有許多的控制站或資源類型,就不會調整結果。Although it can be done, the results don't scale well if you have many controllers or resource types.

使用屬性路由中,就一般定義的路由,這個 uri。With attribute routing, it's trivial to define a route for this URI. 您只會將屬性新增至控制器動作:You simply add an attribute to the controller action:

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

以下是一些其他屬性路由可讓您輕鬆的模式。Here are some other patterns that attribute routing makes easy.

API 版本設定API versioning

在此範例中,"/api/v1/products"路由會傳送到不同的控制器"/api/v2/products"。In this example, "/api/v1/products" would be routed to a different controller than "/api/v2/products".

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

多載的 URI 區段Overloaded URI segments

在此範例中,"1"是順序的數字,但是"pending"對應至集合。In this example, "1" is an order number, but "pending" maps to a collection.

/orders/1 /orders/pending

多個參數類型Multiple parameter types

在此範例中,"1"是順序的數字,但是"2013/06/16"指定的日期。In this example, "1" is an order number, but "2013/06/16" specifies a date.

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

啟用屬性路由Enabling Attribute Routing

若要啟用屬性路由,請呼叫MapHttpAttributeRoutes設定期間。To enable attribute routing, call MapHttpAttributeRoutes during configuration. 這個擴充方法中定義System.Web.Http.HttpConfigurationExtensions類別。This extension method is defined in the System.Web.Http.HttpConfigurationExtensions class.

using System.Web.Http;

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

            // Other Web API configuration not shown.
        }
    }
}

屬性路由可以結合以慣例為基礎路由。Attribute routing can be combined with convention-based routing. 若要定義以慣例為基礎的路由,請呼叫MapHttpRoute方法。To define convention-based routes, call the MapHttpRoute method.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Attribute routing.
        config.MapHttpAttributeRoutes();

        // Convention-based routing.
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

如需有關如何設定 Web API 的詳細資訊,請參閱 設定 ASP.NET Web API 2For more information about configuring Web API, see Configuring ASP.NET Web API 2.

注意:從 Web API 1 進行移轉Note: Migrating From Web API 1

Web API 2 之前, 的 Web API 專案範本會產生如下的程式碼:Prior to Web API 2, the Web API project templates generated code like this:

protected void Application_Start()
{
    // WARNING - Not compatible with attribute routing.
    WebApiConfig.Register(GlobalConfiguration.Configuration);
}

如果已啟用屬性路由,此程式碼會擲回例外狀況。If attribute routing is enabled, this code will throw an exception. 如果您升級現有的 Web API 專案以使用屬性路由,請務必更新這個組態程式碼所示:If you upgrade an existing Web API project to use attribute routing, make sure to update this configuration code to the following:

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

Note

如需詳細資訊,請參閱 < 設定與 ASP.NET 裝載的 Web APIFor more information, see Configuring Web API with ASP.NET Hosting.

新增路由屬性Adding Route Attributes

使用屬性來定義路由的範例如下:Here is an example of a route defined using an attribute:

public class OrdersController : ApiController
{
    [Route("customers/{customerId}/orders")]
    [HttpGet]
    public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }
}

字串"customers/{customerId}/orders"是路由的 URI 範本。The string "customers/{customerId}/orders" is the URI template for the route. Web API 會嘗試比對要求的 URI 範本。Web API tries to match the request URI to the template. 在此範例中,"customers"和"orders"是常值的區段,In this example, "customers" and "orders" are literal segments, and "{customerId}" is a variable parameter. 下列 Uri 會符合此範本:The following URIs would match this template:

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

您可以藉由比對來限制條件約束,本主題稍後所述。You can restrict the matching by using constraints, described later in this topic.

請注意, "{customerId}"路由範本中的參數名稱相符customerId方法中的參數。Notice that the "{customerId}" parameter in the route template matches the name of the customerId parameter in the method. 當 Web API 會叫用控制器動作時,它會嘗試將路由參數繫結。When Web API invokes the controller action, it tries to bind the route parameters. 比方說,如果 URI 為http://example.com/customers/1/orders,Web API 會嘗試將值"1"的繫結customerId動作中的參數。For example, if the URI is http://example.com/customers/1/orders, Web API tries to bind the value "1" to the customerId parameter in the action.

URI 樣板可以有數個參數:A URI template can have several parameters:

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

路由屬性沒有任何控制器方法會使用以慣例為基礎的路由。Any controller methods that do not have a route attribute use convention-based routing. 如此一來,您可以結合這兩種類型的同一個專案中的路由。That way, you can combine both types of routing in the same project.

HTTP 方法HTTP Methods

Web API,也會根據要求的 HTTP 方法 (GET、 POST 等) 的動作。Web API also selects actions based on the HTTP method of the request (GET, POST, etc). 根據預設,Web API 會尋找不區分大小寫的比對控制器方法名稱的開頭。By default, Web API looks for a case-insensitive match with the start of the controller method name. 例如,名為控制器方法PutCustomers符合 HTTP PUT 要求。For example, a controller method named PutCustomers matches an HTTP PUT request.

您可以覆寫此慣例裝飾的方法與任何下列屬性:You can override this convention by decorating the method with any the following attributes:

  • [HttpDelete][HttpDelete]
  • [HttpGet][HttpGet]
  • [HttpHead][HttpHead]
  • [HttpOptions][HttpOptions]
  • [HttpPatch][HttpPatch]
  • [HttpPost][HttpPost]
  • [HttpPut][HttpPut]

下列範例會將 CreateBook 方法對應至 HTTP POST 要求。The following example maps the CreateBook method to HTTP POST requests.

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

對於所有其他 HTTP 方法,包括非標準的方法,請使用AcceptVerbs屬性,它會使用 HTTP 方法清單。For all other HTTP methods, including non-standard methods, use the AcceptVerbs attribute, which takes a list of HTTP methods.

// WebDAV method
[Route("api/books")]
[AcceptVerbs("MKCOL")]
public void MakeCollection() { }

路由前置詞Route Prefixes

通常中的路由所有以相同的前置詞開頭的控制站。Often, the routes in a controller all start with the same prefix. 例如: For example:

public class BooksController : ApiController
{
    [Route("api/books")]
    public IEnumerable<Book> GetBooks() { ... }

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

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

您也可以使用整個控制器設定一般的前置詞 [RoutePrefix] 屬性:You can set a common prefix for an entire controller by using the [RoutePrefix] attribute:

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
    // GET api/books
    [Route("")]
    public IEnumerable<Book> Get() { ... }

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

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

方法的屬性上使用波狀符號 (~),來覆寫的路由前置詞:Use a tilde (~) on the method attribute to override the route prefix:

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

    // ...
}

路由前置詞可以包含參數:The route prefix can include parameters:

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

路由條件約束Route Constraints

路由條件約束可讓您限制如何比對路由範本中的參數。Route constraints let you restrict how the parameters in the route template are matched. 一般語法"{參數: 條件約束}"。The general syntax is "{parameter:constraint}". 例如: For example:

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

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

在這裡,第一個路由將只會選取如果"識別碼"URI 區段是一個整數。Here, the first route will only be selected if the "id" segment of the URI is an integer. 否則,將會選擇第二個路由。Otherwise, the second route will be chosen.

下表列出支援的條件約束。The following table lists the constraints that are supported.

條件約束Constraint 描述Description 範例Example
alphaalpha 比對大寫或小寫英文字母字元 (a-z、 A-Z)Matches uppercase or lowercase Latin alphabet characters (a-z, A-Z) {x: alpha}{x:alpha}
boolbool 布林值,會比對。Matches a Boolean value. {x: bool}{x:bool}
datetimedatetime 相符項目DateTime值。Matches a DateTime value. {x: datetime}{x:datetime}
decimaldecimal 符合十進位值。Matches a decimal value. {x:decimal}{x:decimal}
doubledouble 比對 64 位元浮點值。Matches a 64-bit floating-point value. {x:double}{x:double}
floatfloat 比對 32 位元浮點值。Matches a 32-bit floating-point value. {x: float}{x:float}
guidguid 比對的 GUID 值。Matches a GUID value. {x: guid}{x:guid}
intint 比對 32 位元整數值。Matches a 32-bit integer value. {x: int}{x:int}
長度length 符合使用指定的長度,或在指定的長度範圍內的字串。Matches a string with the specified length or within a specified range of lengths. {x:length(6)} {x:length(1,20)}{x:length(6)} {x:length(1,20)}
longlong 比對 64 位元整數值。Matches a 64-bit integer value. {x: 長時間}{x:long}
maxmax 比對的最大值的整數。Matches an integer with a maximum value. {x:max(10)}{x:max(10)}
maxlengthmaxlength 符合最大長度的字串。Matches a string with a maximum length. {x:maxlength(10)}{x:maxlength(10)}
minmin 符合最小值的整數。Matches an integer with a minimum value. {x: min(10)}{x:min(10)}
minlengthminlength 符合最小長度的字串。Matches a string with a minimum length. {x: minlength(10)}{x:minlength(10)}
rangerange 比對的值範圍內的整數。Matches an integer within a range of values. {x: range(10,50)}{x:range(10,50)}
regexregex 符合規則運算式。Matches a regular expression. {x:regex(^\d{3}-\d{3}-\d{4}$)}{x:regex(^\d{3}-\d{3}-\d{4}$)}

請注意一些條件約束,例如"min",接受括號括住的引數。Notice that some of the constraints, such as "min", take arguments in parentheses. 您可以將多個條件約束套用至參數,並以冒號分隔。You can apply multiple constraints to a parameter, separated by a colon.

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

自訂路由限制式Custom Route Constraints

您可以建立自訂路由條件約束,藉由實作IHttpRouteConstraint介面。You can create custom route constraints by implementing the IHttpRouteConstraint interface. 例如,下列條件約束限制參數為非零的整數值。For example, the following constraint restricts a parameter to a non-zero integer value.

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;
    }
}

下列程式碼示範如何註冊條件約束:The following code shows how to register the constraint:

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

        config.MapHttpAttributeRoutes(constraintResolver);
    }
}

現在您可以套用條件約束,在您的路由:Now you can apply the constraint in your routes:

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

您也可以取代整個DefaultInlineConstraintResolver藉由實作類別IInlineConstraintResolver介面。You can also replace the entire DefaultInlineConstraintResolver class by implementing the IInlineConstraintResolver interface. 這樣將會取代所有的內建的條件約束,除非您實作IInlineConstraintResolver特別將其加入。Doing so will replace all of the built-in constraints, unless your implementation of IInlineConstraintResolver specifically adds them.

選擇性 URI 參數和預設值Optional URI Parameters and Default Values

您可以讓 URI 參數選擇性加入路由參數的問號。You can make a URI parameter optional by adding a question mark to the route parameter. 如果路由參數為選擇性的您必須定義方法參數的預設值。If a route parameter is optional, you must define a default value for the method parameter.

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

在此範例中,/api/books/locale/1033/api/books/locale傳回相同的資源。In this example, /api/books/locale/1033 and /api/books/locale return the same resource.

或者,您可以依下列方式指定在路由範本中,預設值:Alternatively, you can specify a default value inside the route template, as follows:

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

這是上述範例中,幾乎相同,但有所些微差異的行為時所套用的預設值。This is almost the same as the previous example, but there is a slight difference of behavior when the default value is applied.

  • 在第一個範例 ("{lcid:int?}") 中,1033年的預設值是直接指派給方法參數,讓參數會將此的確切值。In the first example ("{lcid:int?}"), the default value of 1033 is assigned directly to the method parameter, so the parameter will have this exact value.
  • 在第二個範例中 ("{lcid:int = 1033年}"),"1033"的預設值會透過模型繫結程序。In the second example ("{lcid:int=1033}"), the default value of "1033" goes through the model-binding process. 預設模型繫結器將轉換成數值 1033年"1033"。The default model-binder will convert "1033" to the numeric value 1033. 不過,您可以插入可能會執行一些不同的自訂模型繫結器。However, you could plug in a custom model binder, which might do something different.

(在大部分情況下,除非您有自訂模型繫結器在管線中,兩種形式會是對等)。(In most cases, unless you have custom model binders in your pipeline, the two forms will be equivalent.)

路由名稱Route Names

在 Web API 中,每個路由會有一個名稱。In Web API, every route has a name. 路由名稱適合用於產生連結,以便您可以在 HTTP 回應中包含的連結。Route names are useful for generating links, so that you can include a link in an HTTP response.

指定的路由名稱,請設定名稱屬性上的屬性。To specify the route name, set the Name property on the attribute. 下列範例會示範如何設定路由名稱,以及如何產生連結時所使用的路由名稱。The following example shows how to set the route name, and also how to use the route name when generating a link.

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;
    }
}

路由順序Route Order

當此架構會嘗試比對路由的 URI 時,它會評估以特定順序的路由。When the framework tries to match a URI with a route, it evaluates the routes in a particular order. 若要指定的順序,將順序屬性路由。To specify the order, set the Order property on the route attribute. 會先評估較低的值。Lower values are evaluated first. 預設順序值為零。The default order value is zero.

以下是如何決定總排序:Here is how the total ordering is determined:

  1. 比較順序路由屬性的屬性。Compare the Order property of the route attribute.

  2. 查看路由範本中的每個 URI 區段。Look at each URI segment in the route template. 對於每個區段中,排序,如下所示:For each segment, order as follows:

    1. 常值的區段。Literal segments.
    2. 路由條件約束使用的參數。Route parameters with constraints.
    3. 路由參數,而不需要條件約束。Route parameters without constraints.
    4. 使用條件約束的萬用字元參數區段。Wildcard parameter segments with constraints.
    5. 萬用字元參數區段,而不需要條件約束。Wildcard parameter segments without constraints.
  3. 在繫結的情況下,路由會依不區分大小寫的序數字串比較 (OrdinalIgnoreCase) 的路由範本。In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.

以下是一個範例。Here is an example. 假設您會定義下列控制器:Suppose you define the following controller:

[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) { ... }
}

這些路由會排序,如下所示。These routes are ordered as follows.

  1. orders/detailsorders/details
  2. orders/{id}orders/{id}
  3. orders/{customerName}orders/{customerName}
  4. orders/{*date}orders/{*date}
  5. orders/pendingorders/pending

請注意,"details"是常值的區段以及"{id}"之前出現,但"pending"會顯示上次因為順序屬性為 1。Notice that "details" is a literal segment and appears before "{id}", but "pending" appears last because the Order property is 1. (這個範例假設沒有客戶名叫做"details"或"pending"。(This example assumes there are no customers named "details" or "pending". 一般情況下,請盡量避免模稜兩可的路由。In general, try to avoid ambiguous routes. 在此範例中,較佳的路由範本,如GetByCustomer是'customers/{customerName}")In this example, a better route template for GetByCustomer is "customers/{customerName}" )