首页

Attribute Routing 在 Asp.net Webapi 中的使用

环境要求

Visual Studio 2013 以及更高版本


为什么要使用 Attribute Routing

在实际的应用中,我们常常需要符合用户理解的url,这通过基于约定的路由方式实现起来比较困难,尤其要实现大量的定制的url的时候:

/customers/1/orders

通过 Attribute Routing 就会很简单,你只需要添加 action 的属性即可:

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


启用 Attribute Routing

在 App_Start/WebApiConfig.cs 文件中添加下面的配置就可以,默认是添加的,不需要自己添加,如果要禁止使用,去掉这句:

config.MapHttpAttributeRoutes();

在 Global.asax 文件中,你可以看见注册的语句,这是默认添加的,如果没有你需要自己手动添加:

GlobalConfiguration.Configure(WebApiConfig.Register);


添加 Attribute Routing

public class OrdersController : ApiController

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

customerId 是模版参数,属性上的与方法参数一一对应,下面的 url 将与上面的路由匹配:

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


Route 前缀

通常,在控制器中的路由,都是相同的前缀,所以可以提取这前缀到控制器类:

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

可以用一个(~)重写路由前缀:

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

Route 约束

Route约束可以限制模版参数的类型,语法 {parameter:constraint} :

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

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


下面列表包含更多的被支持的约束

ConstraintDescriptionExample
alphaMatches uppercase or lowercase Latin alphabet characters (a-z, A-Z){x:alpha}
boolMatches a Boolean value.{x:bool}
datetimeMatches a DateTime value.{x:datetime}
decimalMatches a decimal value.{x:decimal}
doubleMatches a 64-bit floating-point value.{x:double}
floatMatches a 32-bit floating-point value.{x:float}
guidMatches a GUID value.{x:guid}
intMatches a 32-bit integer value.{x:int}
lengthMatches a string with the specified length or within a specified range of lengths.{x:length(6)}
{x:length(1,20)}
longMatches a 64-bit integer value.{x:long}
maxMatches an integer with a maximum value.{x:max(10)}
maxlengthMatches a string with a maximum length.{x:maxlength(10)}
minMatches an integer with a minimum value.{x:min(10)}
minlengthMatches a string with a minimum length.{x:minlength(10)}
rangeMatches an integer within a range of values.{x:range(10,50)}
regexMatches a regular expression.{x:regex(^\d{3}-\d{3}-\d{4}$)}

注意,一些特殊的约束,比如 min 的用法。你能同时使用多个约束,用冒号分隔:

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


定制 Route 约束

用户可以通过重写这个接口 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) { ... }


可选的 Url 参数以及默认值

通过(?)可以定义可选的 url 参数,如果一个 url 可选,那么方法参数必须定义默认值:

[Route("api/books/locale/{lcid:int?}")]
public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
下面的连接将匹配上面的路由:

/api/books/locale/1033

/api/books/locale

或者如下面的写法,效果与几乎(在自定义模型绑定上会不一样)与上面的一样:

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


Route 名字

在webapi中,每个 Route 都有一个名字,在生成 url 上,这个名字很有用,指定 Route 名字很容易:

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 顺序

当框架在使用一个路由去匹配一个 url 的时候,它按照一个特定的顺序,需要指定一个顺序,可以使用 RouteOrder,它默认值是 0:

  1. 比较 RouteOrder 值大小,小的优先.
  2. 比较路由模版中的 url 块:
    1. 固定值片段
    2. 带约束的路由参数
    3. 不带约束的路由参数
    4. 带约束的通配符路由参数
    5. 不带约束的通配符路由参数

用这个例子来解析它们的路由顺序:

[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
from 爱施园
Posted by 森林 on 2018/01/12
Copyright ©2018 爱施园 粤ICP备14091834号