WebApi2官网学习记录---Attribute Routing

从WebApi 1迁移到WebAPI 2要改变配置代码如下:

WebApi 1:

protected void Application_Start()

{

    // WARNING - Not compatible with attribute routing.

    WebApiConfig.Register(GlobalConfiguration.Configuration);

}

WebAPI 2:

protected void Application_Start()

{

    // Pass a delegate to the Configure method.

    GlobalConfiguration.Configure(WebApiConfig.Register);

}

添加一个Route Attributes

public class OrdersController : ApiController

{

    [Route("customers/{customerId}/orders")]

    [HttpGet]

    public IEnumerable<Order> FindOrdersByCustomer(int customerId) { ... }

}

可以匹配一下URL: 

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

匹配http post请求的CreateBook

[Route("api/books")]

[HttpPost]

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

[AcceptVerbs("MKCOL")]

public void MakeCollection() { 

Route Prefixes

 对于同一个controller路由有相同的前缀, 此时可以使用 [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) { ... }

}

可以在方法的attribute中使用(~)重写路由前缀  

[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("users/{id:int}"]

public User GetUserById(int id) { ... }



[Route("users/{name}"]

public User GetUserByName(string name) { ... }

       支持的约束如下表所示: 

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",可以对路由中的参数应用多个约束,用冒号分割约束     

[Route("users/{id:int:min(1)}")]

public User GetUserById(int id) { ... }

 自定义路由约束

WebApi2官网学习记录---Attribute Routing
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;

    }

}



修改WebAPIConfig.cs

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

可选的URI参数和默认值

 第一种方式,默认值直接分配给了方法参数,因此参数获得的是精确的值

 第二种方式,默认值通过model-binding进行处理,默认的model-binder将字符串类型的默认值"1033"转为int形的1033,对于model-binder可能有所不同。

WebApi2官网学习记录---Attribute Routing
 1 第一种写法

 2 public class BooksController : ApiController

 3 {

 4     [Route("api/books/locale/{lcid:int?}")]

 5     public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }

 6 }

 7 

 8 第二种写法

 9 public class BooksController : ApiController

10 {

11     [Route("api/books/locale/{lcid:int=1033}")]

12     public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }

13 }
View Code

Route Names

 web api中每个路由有一个name,Route names可以用来生成links

WebApi2官网学习记录---Attribute Routing
 1 public class BooksController : ApiController

 2 {

 3     [Route("api/books/{id}", Name="GetBookById")]

 4     public BookDto GetBook(int id) 

 5     {

 6         // Implementation not shown...

 7     }

 8 

 9     [Route("api/books")]

10     public HttpResponseMessage Post(Book book)

11     {

12         // Validate and add book to database (not shown)

13 

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

15 

16         // Generate a link to the new book and set the Location header in the response.

17         string uri = Url.Link("GetBookById", new { id = book.BookId });

18         response.Headers.Location = new Uri(uri);

19         return response;

20     }

21 }
View Code

Route Order

 通过设置RouteOrder属性在route attribute上,可以指定Route的顺序,值越小先被计算,默认顺序对应的值是0

  路由顺序的选择:

  • 比较RouteOrder属性
  • 对于URI部分按如下进行排序
  1. URI的固定部分(literal segments)  
  2. 带约束的路由参数
  3. 不带约束的路由参数
  4. 带约束的通配符
  5. 不带约束的通配符
  • route的排序不区分大小写的     
[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/{customreName}
  4. orders/{*date}
  5. orders/pending

 Route Attribute的使用完整例子:  

WebApi2官网学习记录---Attribute Routing
  1 using BooksAPI.DTOs;

  2 using BooksAPI.Models;

  3 using System;

  4 using System.Data.Entity;

  5 using System.Linq;

  6 using System.Linq.Expressions;

  7 using System.Threading.Tasks;

  8 using System.Web.Http;

  9 using System.Web.Http.Description;

 10 

 11 namespace BooksAPI.Controllers

 12 {

 13     [RoutePrefix("api/books")]

 14     public class BooksController : ApiController

 15     {

 16         private BooksAPIContext db = new BooksAPIContext();

 17 

 18         // Typed lambda expression for Select() method. 

 19         private static readonly Expression<Func<Book, BookDto>> AsBookDto =

 20             x => new BookDto

 21             {

 22                 Title = x.Title,

 23                 Author = x.Author.Name,

 24                 Genre = x.Genre

 25             };

 26 

 27         // GET api/Books

 28         [Route("")]

 29         public IQueryable<BookDto> GetBooks()

 30         {

 31             return db.Books.Include(b => b.Author).Select(AsBookDto);

 32         }

 33 

 34         // GET api/Books/5

 35         [Route("{id:int}")]

 36         [ResponseType(typeof(BookDto))]

 37         public async Task<IHttpActionResult> GetBook(int id)

 38         {

 39             BookDto book = await db.Books.Include(b => b.Author)

 40                 .Where(b => b.BookId == id)

 41                 .Select(AsBookDto)

 42                 .FirstOrDefaultAsync();

 43             if (book == null)

 44             {

 45                 return NotFound();

 46             }

 47 

 48             return Ok(book);

 49         }

 50 

 51         [Route("{id:int}/details")]

 52         [ResponseType(typeof(BookDetailDto))]

 53         public async Task<IHttpActionResult> GetBookDetail(int id)

 54         {

 55             var book = await (from b in db.Books.Include(b => b.Author)

 56                               where b.AuthorId == id

 57                               select new BookDetailDto

 58                               {

 59                                   Title = b.Title,

 60                                   Genre = b.Genre,

 61                                   PublishDate = b.PublishDate,

 62                                   Price = b.Price,

 63                                   Description = b.Description,

 64                                   Author = b.Author.Name

 65                               }).FirstOrDefaultAsync();

 66 

 67             if (book == null)

 68             {

 69                 return NotFound();

 70             }

 71             return Ok(book);

 72         }

 73 

 74         [Route("{genre}")]

 75         public IQueryable<BookDto> GetBooksByGenre(string genre)

 76         {

 77             return db.Books.Include(b => b.Author)

 78                 .Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))

 79                 .Select(AsBookDto);

 80         }

 81 

 82         [Route("~api/authors/{authorId}/books")]

 83         public IQueryable<BookDto> GetBooksByAuthor(int authorId)

 84         {

 85             return db.Books.Include(b => b.Author)

 86                 .Where(b => b.AuthorId == authorId)

 87                 .Select(AsBookDto);

 88         }

 89 

 90         [Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]

 91         [Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]

 92         public IQueryable<BookDto> GetBooks(DateTime pubdate)

 93         {

 94             return db.Books.Include(b => b.Author)

 95                 .Where(b => DbFunctions.TruncateTime(b.PublishDate)

 96                     == DbFunctions.TruncateTime(pubdate))

 97                 .Select(AsBookDto);

 98         }

 99 

100         protected override void Dispose(bool disposing)

101         {

102             db.Dispose();

103             base.Dispose(disposing);

104         }

105     }

106 }
View Code

其中以下路由中的匹配符*的作用:

  [Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]

        [Route("date/{pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]  // new

        public IQueryable<BookDto> GetBooks2(DateTime pubdate)

        {

            /*

             * 路由中的*匹配符的作用,是为了告诉route engine占位符{pubdate}应该匹配剩余的URI,

             * 默认情况下,一个模板参数仅仅匹配一个URI。

             * This tells the routing engine that {pubdate} should match the rest of the URI. By default, 

             * a template parameter matches a single URI segment. In this case, we want {pubdate} to 

             * span several URI segments:

             */

           

        }

 

     

 

你可能感兴趣的:(attribute)