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

 自定义路由约束

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 != ;
} string valueString = Convert.ToString(value, CultureInfo.InvariantCulture);
if (Int64.TryParse(valueString, NumberStyles.Integer,
CultureInfo.InvariantCulture, out longValue))
{
return longValue != ;
}
}
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) { ... }

可选的URI参数和默认值

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

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

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

Route Names

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

 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

通过设置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 = )]
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的使用完整例子:

 using BooksAPI.DTOs;
using BooksAPI.Models;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description; namespace BooksAPI.Controllers
{
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
private BooksAPIContext db = new BooksAPIContext(); // Typed lambda expression for Select() method.
private static readonly Expression<Func<Book, BookDto>> AsBookDto =
x => new BookDto
{
Title = x.Title,
Author = x.Author.Name,
Genre = x.Genre
}; // GET api/Books
[Route("")]
public IQueryable<BookDto> GetBooks()
{
return db.Books.Include(b => b.Author).Select(AsBookDto);
} // GET api/Books/5
[Route("{id:int}")]
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
BookDto book = await db.Books.Include(b => b.Author)
.Where(b => b.BookId == id)
.Select(AsBookDto)
.FirstOrDefaultAsync();
if (book == null)
{
return NotFound();
} return Ok(book);
} [Route("{id:int}/details")]
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
var book = await (from b in db.Books.Include(b => b.Author)
where b.AuthorId == id
select new BookDetailDto
{
Title = b.Title,
Genre = b.Genre,
PublishDate = b.PublishDate,
Price = b.Price,
Description = b.Description,
Author = b.Author.Name
}).FirstOrDefaultAsync(); if (book == null)
{
return NotFound();
}
return Ok(book);
} [Route("{genre}")]
public IQueryable<BookDto> GetBooksByGenre(string genre)
{
return db.Books.Include(b => b.Author)
.Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
.Select(AsBookDto);
} [Route("~api/authors/{authorId}/books")]
public IQueryable<BookDto> GetBooksByAuthor(int authorId)
{
return db.Books.Include(b => b.Author)
.Where(b => b.AuthorId == authorId)
.Select(AsBookDto);
} [Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
[Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
return db.Books.Include(b => b.Author)
.Where(b => DbFunctions.TruncateTime(b.PublishDate)
== DbFunctions.TruncateTime(pubdate))
.Select(AsBookDto);
} protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}

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

  [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:
*/ }

WebApi2官网学习记录---Attribute Routing的更多相关文章

  1. WebApi2官网学习记录---Configuring

    Configuration Settings WebAPI中的configuration settings定义在HttpConfiguration中.有一下成员: DependencyResolver ...

  2. WebApi2官网学习记录---Cookie

    Cookie的几个参数: Domain.Path.Expires.Max-Age 如果Expires与Max-Age都存在,Max-Age优先级高,如果都没有设置cookie会在会话结束后删除cook ...

  3. WebApi2官网学习记录---批量处理HTTP Message

    原文:Batching Handler for ASP.NET Web API 自定义实现HttpMessageHandler public class BatchHandler : HttpMess ...

  4. WebApi2官网学习记录---Html Form Data

    HTML Forms概述 <form action="api/values" method="post"> 默认的method是GET,如果使用GE ...

  5. WebApi2官网学习记录--HttpClient Message Handlers

    在客户端,HttpClient使用message handle处理request.默认的handler是HttpClientHandler,用来发送请求和获取response从服务端.可以在clien ...

  6. WebApi2官网学习记录--HTTP Message Handlers

    Message Handlers是一个接收HTTP Request返回HTTP Response的类,继承自HttpMessageHandler 通常,一些列的message handler被链接到一 ...

  7. WebApi2官网学习记录--- Authentication与Authorization

    Authentication(认证)   WebAPI中的认证既可以使用HttpModel也可以使用HTTP message handler,具体使用哪个可以参考一下依据: 一个HttpModel可以 ...

  8. WebApi2官网学习记录---单元测试

    如果没有对应的web api模板,首先使用nuget进行安装 例子1: ProductController 是以硬编码的方式使用StoreAppContext类的实例,可以使用依赖注入模式,在外部指定 ...

  9. WebApi2官网学习记录---Tracing

    安装追踪用的包 Install-Package Microsoft.AspNet.WebApi.Tracing Update-Package Microsoft.AspNet.WebApi.WebHo ...

随机推荐

  1. 在 sys.servers 中找不到服务器的解决办法,自己解决的

    一开始提示,在服务器中找不到在 sys.servers 中找不到服务器 'QPAccountsDBLink',先用select * from sys.servers  ,发现只能查到一个服务器名称,后 ...

  2. 《第一行代码》学习笔记37-服务Service(4)

    一个比较完整的自定义AsyncTask写成如下: class DownloadTask extends AsyncTask<Void, Integer, Boolean> { @Overr ...

  3. mysql 分区表详解

    项目中要一张库表实现 list分区.并且支持多种数据库. oracle 很顺利,只是mysql 听说5.1版本就已经支持了, 可是试了很多个版本,都不行,后来查到原因是要5.5 以上版本 分区才支持 ...

  4. ORACLE基本SQL语句-用户及建表篇

    一.用户相关SQL语句 /*新建用户*/create user ; 说明:SA用户名,2013密码 /*授权connect,resource给用户sa*/grant connect,resource ...

  5. CGRectInset CGRectoffset UIEdgeInsetsInsetRect 这三个函数的使用情况

    //CGRectInset 将原来的矩形放大或者缩小,正表示缩小,-表示放大. CGRect rect= CGRectMake(20, 50, 100, 80); CGRect rect1=CGRec ...

  6. cell高度自动适应文章内容

    效果: 描述:表视图中生成多个不同的cell,cell的高度跟文字内容的多少有关 要求:需要自己在网上下载一个plis文件,然后修改两个标题 一 : 创建工程文件UIAutomaticCellHeig ...

  7. poj3685 二分套二分

    F - 二分二分 Crawling in process... Crawling failed Time Limit:6000MS     Memory Limit:65536KB     64bit ...

  8. angular ng-bind-html 对src路径失效 解决方案

    json内容 ;<img src="/newsfile/1506271512489.jpg" width="600" height="450&q ...

  9. php之类,对象(二)继承性,static静态的,const常量

    三大特性 之二 继承性: 1.概念:如果一个类有子类,那么该子类会继承父类的一切东西,但私有成员访问不到. 2.在定义子类时需要加关键字:extends class Text extends Info ...

  10. 将含有父ID的列表转成树

    我们知道数据库一般是以一个列表(id,pid)的形式保存树的.如何提取这棵树呢?最简单的方法就是根据pid循环查表.但是毫无疑问,这会产生巨大的数据库查询开销. 那么一般建议的方法是一次性将全部相关数 ...