ASP.NET Web API 中的属性路由

为什么要有属性路由 

基于约定路由的一个优点是模板在单个位置中定义,并且路由规则在所有控制器上一致的应用。但是基于约定的路由很难支持RESTFUl 中常见的某些URI模式。例如,资源通常包含子资源,客户有订单,电影有演员,书有作者等等。创建反应这些URI是很自然的,如下图所示:

/customers/1/orders

 使用属性路由,为此URI定义路由很简单,只需向控制器中添加一个属性,如下图所示:

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

 


启用属性路由 

要启用属性路由,要在配置期间调用MapHttpAttributeRoutes,此方法在System.Web.Http.HttpConfigurationExtensions类中定义。如下图所示: 

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

属性路由可以和约定路由相结合,要定义基于约定的路由,可以调用MapHttpRoute方法。如下图所示: 

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

添加属性路由 

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

字符串“customers/{customerId}/orders”是路径的URI模板,Web API尝试将请求URI和模板匹配。在此示例中,“customers”和“orders”是静态片段,{customerId}是可变参数,以下URI将与此模板匹配:

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

请注意:路由模板中的{customerId}参数要和方法中customerId相匹配,当WebAPI调用控制器操作时,它会尝试绑定路由参数。例如 URI为 http://example.com/customers/1/orders ,则Web API会尝试将 值“1”绑定到操作的customerId参数中。

URI模板中可以有多个参数,如下图所示:

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

 

Web API还根据请求的Http方法(Get,Post等)选择操作。默认情况下,Web API会查找与控制器方法名称的开头不区分大小写的匹配项。例如,控制器方法PutCustomer匹配HTTP PUT请求。 

以下示例将CreateBook方法映射到HTTP POST请求。 

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

对于所有其他HTTP方法(包括非标准方法),请使用AcceptVerbs属性,该属性采用HTTP方法列表。 

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

 


路由前缀 

通常,控制器的中的路由都以相同的前缀开头,例如: 

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]属性为整个控制器设置公共前缀: 

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

在method属性上使用波浪号(〜)来覆盖路由前缀: 

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

 这时候,只有id是整数时,才匹配第一路径。否则,将选择第二路径。完整的约束列表如下图所示:

约束描述例子
alpha匹配大写或者小写拉丁字母:(a-z , A-Z){x:alpha}
bool匹配布尔类型{x:bool}
datetime匹配DataTime类型{x:datetime}
decimal匹配小数值{x:decimal}
double匹配64位浮点数{x:double}
float匹配32位浮点数{x:float}
guid匹配Guid值{x:guid}
int匹配32位整型{x:int}
length匹配具有指定长度或者具有指定长度范围的字符串{x:length(6)} {x:length(1,20)}
long匹配64位整数值{x:long}
max匹配具有最大值的整数{x:max(10)}
maxlength匹配具有最大长度的字符串{x:maxlength(10)}
min匹配具有最小值的整数{x:min(10)}
minlength匹配具有最小长度的字符串{x:minlength(10)}
range匹配值范围内的整数{x:range(10,50)}
regex匹配正则表达式{x:regex(^\d{3}-\d{3}-\d{4}$)}

可以将多个约束应用于参数,以冒号分隔 

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

 

可以通过实现IHttpRouteConstraint,来创建自定义路由约束。例如,以下约束将参数限制为非零整数值。 

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

可以在路由中应用约束: 

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

 


可选的URI参数和默认值 

 可以通过向路由参数添加问号来使URI参数可选。如果route参数是可选的,则必须为方法参数定义默认值

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

路由名称 

在Web API 中,每个路由都有一个名称。路由名称对于对于生成链接十分有用,因此可以在HTTP响应中包含链接。

要指定路由名称,请在属性上设置“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与路由匹配时,它会按特定的顺序评估路由,要指定顺序,需要在Route属性上设置Order属性,框架首先评估Order值较低的方法,默认Order值为零。

以下是确定总排序的方式:

1、比较Route属性的Order属性。

2、查看路由模板中的每个URI片段,对于细分,约定如下:

      a、静态片段。

      b、使用约束路由参数。

      c、路由参数没有约束。

      d、具有约束的通配符参数段

      e、没有约束的通配符参数段

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

以上路由将按以下顺序执行:

1、orders/details 

2、orders/{id}

3、orders/{customerName}

4、orders/{*date}

5、orders/pending

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值