特性路由 是Web API 2 中提出的一种新的类型的路由,正如其名称那样,它是经过特性(Attribute) 来定义路由的,相比以前的基于模式(Convertion Based)的路由,特性路由 可以提供更为灵活和更多的控制。更好的方式是,灵活的组合使用这两种方式。正则表达式
在 特性路由 以前 的 基于模式 的路由,咱们须要定义一些包含一些参数化字符串的模板,例如,api/{congroller}/{action}/{id}
,当接受到请求后,会将请求的 URI 与这些模板进行匹配,这种方式有一个优势那就是全部的路由定义均可以在同一个地方进行配置,这些规则将被应用到全部的 Controller,这也形成也其对一些特定的 URI 的匹配不够灵活,例如,请求的一些资源(Resource) 包含一些子资源,例如顾客具备订单,电影包含演员,书籍具备做者等,这时天然而然的会建立以下的 URI 来映射这种关系:/customers/1/orders
。此时若是使用 基于模式 的方式来定义路由规则便会极为的困难,即便可以处理,在具备较多 Controller 和 资源类型时,也并不能达到很好的效果。
使用特性路由 就能够很好的解决这样的问题,像上面的例子,使用 特性路由 能够很方便的定义知足条件的路由规则,以下所示:express
[Route("customers/{customerId}/orders")] public IEnumerable<Order> GetOrdersByCustomer(int customerId) { ... }
为了启用特性路由,须要调用 System.Web.Http.HttpConfigurationExtensions 的扩展方法 MapHttpAttributeRoutes
进行配置。在 App_Start
目录的 WebApiConfig
类中进行配置。c#
public static void Register(HttpConfiguration config) { // Web API 路由 config.MapHttpAttributeRoutes(); //Convention-based Route config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
下面有一些使用 特性路由 的技巧api
api/v1/products
api/v2/products
/// <summary> /// 根据商品名称获取商品 /// </summary> /// <param name="name"></param> /// <returns></returns> [Route("product/{name}")] public IHttpActionResult GetProduct(string name) { if (string.IsNullOrEmpty(name)) return Ok(products); var product = products.Where(p => { if (p.Name == name) return true; return false; }).FirstOrDefault(); if(product != null){ return Ok(product); } else { return NotFound(); } } /// <summary> /// 返回商品列表 /// </summary> /// <returns></returns> [Route("product")] public IHttpActionResult GetProduct() { return Ok(products); }
/// <summary> /// 根据商品Id获取商品信息 /// </summary> /// <param name="id"></param> /// <returns></returns> [Route("api/product/{id:int}")] public IHttpActionResult GetProduct(int id) { } /// <summary> /// 根据上市时间获取商品 /// </summary> /// <param name="marketdate">上市时间</param> /// <returns></returns> [Route("api/product/{*marketdate:datetime}")] public IHttpActionResult GetProduct(DateTime marketdate) { }
仔细观察,上面配置的特性路由都具备相同的前缀 api/product/
, 使用路由前缀(Route Prefix)咱们能够在 Controller 进行统一设置,例如前面的例子,咱们就能够改成下面的样子:ide
[RoutePrefix("api/product")] public class DefaultController : ApiController { [Route("{id:int}")] public IHttpActionResult GetProduct(int id){ } }
咱们还可使用 ~
符号对使用了路由前缀的 Controller 中的方法进行路由重写,例如:ui
[RoutePrefix("api/product")] public class DefaultController : ApiController { [Route("~/api/discount/product/{id:int}")] public IHttpActionResult GetProduct(int id){ }
上面的例子使用 ~
对路由进行了重写,新的路由规则将覆盖 Controller 定义的路由前缀,如今该方法所匹配的规则为 api/discount/product/{id:int}
,而非原来的 api/product/{id:int}
。
在定义路由前缀时,还能够包含一些参数,例如:code
[RoutePrefix(api/{customerid})] public class DefaultController : ApiController{ [Route("order")] public IEnumerable<Order> GetOrders(int customerid){ } }
咱们可使用 ?
将一个路由参数标记为 可选参数(optional parameter),若是一个路由参数被标记为可选的,则必须为其设置默认值。排序
[Route("api/product/{id:int?}")] public IHttpActionResult GetProduct(int id =1){ }
此时,对于 /api/product/id/1
和 /api/product
将返回相同的结果。
咱们不只能够在方法中设置参数的默认值,还能够在路由特性中进行设置。以下所示,接口
[Route("api/product/{id:int=1}")] public IHttpActionResult GetProduct(int id){ }
上面设置默认值的方法基本彻底相同,可是在应用该值时仍是有细微的区别,以下所示:
ci
1
是直接分配给方法的参数的,所以其老是具备肯定的值"1"
被转换为 System.Int32 类型的 1,然而,咱们能够定义本身的模型绑定器,所以,最终参数 id 的默认值并非肯定的,由于其在自定义的模型绑定器中可能被转换为其它的结果。一般状况下,使用两种形式给予参数默认值的形式的结果是相同的。
经过路由参数约束咱们能够将路由模板中的参数限制为指定的类型,通用的语法以下所示:{parameter:costraint}
,例如前面的例子,将 id
的类型限制为 System.Int32
[Route("api/product/{id:int}")] public IHttpActionResult GetProduct(int id){} [Route("api/product/name")] public IHttpActionResult GetProduct(string name){}
只有请求的参数为 Int 类型时才会匹配第一个路由规则,不然匹配第二个。
下表罗列了可用的约束,
约束 | 描述 | 例子 |
---|---|---|
alpha | 将参数约束为大写或者小写的拉丁字母(a-z,A-Z) | {x:alpha} |
bool | 将参数限制为 bool 类型 | {x:bool} |
datetime | 将参数限制为 date | {x:date} |
decimal | 将参数限制为 decimal 类型 | {x:decimal} |
float | 将参数限制为 32 位浮点数类型 | {x:float} |
double | 将参数限制为 64 位浮点数类型 | {x:double} |
int | 将参数限制为 32 整形 | {x:int} |
long | 将参数限制为 64位整形 | {x:long} |
guid | 将参数类型限制为 guid 类型 | {x:guid} |
length | 将参数的长度限制为指定长度或指定范围的长度 | {x:length(5)/{x:length(1,10)} |
min/max | 限制参数(整形)最大或最小值 | {x:min(10)}/{x:max(10)} |
minlength/maxlength | 限制参数的最小长度或最大长度 | {x:minlength(1)}/{x:maxlength} |
range | 限制参数(整形) 的范围,包含两端 | {x:range(1,3)} |
regex | 限制参数必须匹配指定的正则表达式 | {x:regex(\expression)} |
能够同时使用多个约束条件,每一个条件之间经过 :
进行分割,例如
[Route("api/product/{id:int:min(1)}")] public IHttpActionResult GetProduct(int id){}
除了 内置的约束条件,咱们还能够定制本身的约束条件,方法是实现 IHttpConstraint
接口,重写其 public bool Match( HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection )
方法
而后经过以下的方式注册自定义的约束后,即可像使用内置约束那样的使用自定义约束了
public static class WebApiConfig { public static void Register(HttpConfiguration config) { var constraintResolver = new DefaultInlineConstraintResolver(); constraintResolver.ConstraintMap.Add(identity, typeof(CustomConstrainType)); config.MapHttpAttributeRoutes(constraintResolver); } }
Web API 在选择 Action 时还会基于请求的 Http 方法进行判断, Web Api 默认会匹配控制器方法的开端(匹配不区分大小写),例如,GetProduct
便会被识别为一个 Http Get
方法。咱们可使用内置的一些 Http Method 特性来覆盖默认的实现。
下面的方法,使用 HttpPost 特性将其标记为 POST 方法
[HttpPost] [Route("api/product/{id}")] public IHttpActionResult CreateProduct(){}
除此以外,还可使用AcceptVerbs
特性来实现,其接受一个上述特性的列表(方法名字符串列表)
在 Web Api 中每一个路由都有本身的名称,这个名称在产生连接是十分的有用。
[Route("{id:int}",Name ="GetProductById")] public Product Get(int id) { var product = products.Where(p => { if (p.Id == id) return true; return false; }).FirstOrDefault(); return product; } [Route("~/api/products/{id:int}")] [HttpGet] public HttpResponseMessage Find(int id) { var response = Request.CreateResponse(HttpStatusCode.Created); string uri = Url.Link("GetProductById", new { id = });///api/product/id //response.Headers.Location = new Uri(uri); response.Content = new StringContent(uri); return response; }
当使用一个 Route 去匹配一个 URI 时,会以一个特定的顺序去分析路由,咱们能够设置 Route 的 RouteOrder 来指定一个路由的顺序,RouteOrder 是一个整形数字,默认值为0,其值越小,顺序越靠前。
当接受到一个请求后,会以以下的顺序去匹配路由
[RoutePrefix("orders")] public class OrdersController : ApiController { [Route("{id:int}")] // 具备约束的参数 public HttpResponseMessage Get(int id) { ... } [Route("details")] // 字面值 public HttpResponseMessage GetDetails() { ... } [Route("pending", RouteOrder = 1)] public HttpResponseMessage GetPending() { ... } [Route("{customerName}")] // 无约束条件的参数 public HttpResponseMessage GetByCustomer(string customerName) { ... } [Route("{*date:datetime}")] // 通配符 public HttpResponseMessage Get(DateTime date) { ... }
}
按照上面的规则,能够得出下面的顺序:
orders/details
order/{id:int}
order/{customerName}
order/{*date:datetime}
order/penddig