ASP.NET Web API 2.0新特性:Attribute Routing[上篇]

对于一个针对ASP.NET Web API的调用请求来说,请求的URL和对应的HTTP方法的组合最终决定了目标HttpController的类型和定义其中的目标Action方法。两者之间的映射是通过URL路由来完成的,ASP.NET Web API路由系统提供了一种便捷的方式使我们可以在统一的地方注册适用于所有HttpController的路由。

如果我们能够直接针对目标Action方法进行路由注册,那么我们就能够对路由规则进行细粒度的控制。从设计角度来讲,Web API采用REST架构风格,其URL更多是对目标资源的反映,定义在同一个HttpController中的众多Action方法体现在对一组相关资源的操作。目标资源往往具有一定地层次结构,RESTful的URL应该尽可能地反映这种层次目标资源的这种层次结构,能否针对具体的Action方法进行路由映射往往决定了我们能否设计出真正具有REST风格的URL。为此,ASP.NET Web API 2.0对现有路由系统进行了扩展,提供了一种基于特性的路由是我们可以直接将路由直接注册到具体的Action方法上。[本文已经同步到《How ASP.NET Web API Works?》]

目录

HttpRouteInfoProvider特性 
开启针对特性的路由 
基本路由注册 
让URL模板能够尽可能反映资源的层次结构 
为路由变量设置约束 
通配符路由变量 
缺省路由变量 
设置URL前缀 
指定路由名称 
生成HttpRoute在路由表中的顺序

HttpRouteInfoProvider特性

我们将应用到目标Action方法上直接进行路由注册的特性称为HttpRouteInfoProvider特性,因为它实现了System.Web.Http.Routing.IHttpRouteInfoProvider接口。我们在上面已经提到过了,这种基于特性的路由注册方式仅仅是对ASP.NET Web API现有路由系统的扩展,应用到目标方法上用于路由注册的特性最终会被转换成相应的HttpRoute对象。顾名思义,HttpRouteInfoProvider特性旨在提供最终创建这个HttpRoute对象的相关信息。

HttpRouteInfoProvider提供的路由信息集中体现在IHttpRouteInfoProvider接口的几个属性。如下面的代码片断所示,IHttpRouteInfoProvider定义了 三个只读属性,其核心自然是通过属性RouteTemplate表示的URL模板。它的属性RouteName表示最终创建的HttpRoute对象的名称,另一个属性RouteOrder则决定了该HttpRoute对象在路由表中的位置,这个位置最终决定了其被选择的优先级。

   1: public interface IHttpRouteInfoProvider
   2: {
   3:     string     RouteName { get; }
   4:     int        RouteOrder { get; }
   5:     string     RouteTemplate { get; }
   6: }

读者朋友们可能对IHttpRouteInfoProvider接口不太熟悉,但是对实现它的两个特性应该已经很熟悉了,它们分别是AcceptVerbsAttribute和HttpVerbAttribute。在前面介绍“Action的选择”中我们将这两个特性视为用于选择目标Action方法所支持的HTTP方法的ActionHttpMethodProvider特性来介绍的,其实它们除了实现IActionHttpMethodProvider接口之外,还实现了接口IHttpRouteInfoProvider。

AcceptVerbsAttribute

如下所示的AcceptVerbsAttribute的完整定义,当我们将它应用到目标Action方法上的时候,除了以构造函数参数的形式指定所支持的HTTP方法列表(以字符串的形式)之外,还可以指定RouteName、RouteOrder和RouteTemplate属性值以注册一个针对目标方法的路由。

   1: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
   2: public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider, IHttpRouteInfoProvider
   3: {
   4:     public AcceptVerbsAttribute(params string[] methods); 
   5:  
   6:     public Collection<HttpMethod>      HttpMethods { get; }
   7:     public string                      RouteName { get; set; }
   8:     public int                         RouteOrder { get; set; }
   9:     public string                      RouteTemplate { get; set; }
  10: }

对于如上所示的AcceptVerbsAttribute的定义,有一个细节我们只得注意:应用在该类型的AttributeUsageAttribute特性将AllowMultiple属性设置为True,意味着多个AcceptVerbsAttribute特性可以同时应用到同一个Action方法上。对于路由映射来说就意味着我们可以为同一个Action方法注册多个路由,这使我们可以采用不同的URL来访问同一个Action方法。

HttpVerbAttribute

AcceptVerbsAttribute和HttpVerbAttribute的不同之处在于前者可以通过字符串的形式指定多个支持的HTTP方法,而后者仅仅针对某个具体的HTTP方法。如下所示的是HttpVerbAttribute的完整定义,我们从中可以看出表示URL模板的只读属性RouteTemplate需要在构造函数中指定,而另外两个属性RouteName和RouteOrder则是可读可写的。

   1: public abstract class HttpVerbAttribute : Attribute, IActionHttpMethodProvider, IHttpRouteInfoProvider
   2: {
   3:     protected HttpVerbAttribute(HttpMethod httpMethod);
   4:     protected HttpVerbAttribute(HttpMethod httpMethod, string routeTemplate); 
   5:  
   6:     public Collection<HttpMethod>     HttpMethods { get; }
   7:     public string                     RouteName { get; set; }
   8:     public int                        RouteOrder { get; set; }
   9:     public string                     RouteTemplate { get; }
  10: }

针对7种常用的HTTP方法,ASP.NET Web API定义了7个继承自HttpVerbAttribute的类型,它们分别是具有如下定义的HttpGetAttribute、HttpPostAttribute、HttpPutAttribute、HttpDeleteAttribute、HttpPatchAttribute、HttpHeadAttribute和HttpOptionsAttribute。我们可以看到这7个具体的HttpVerbAttribute均定义了两个构造函数重载,如果需要使用它们来注册路由,需要在构造函数中指定相应URL模板。

   1: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
   2: public sealed class HttpGetAttribute : HttpVerbAttribute
   3: {
   4:     public HttpGetAttribute();
   5:     public HttpGetAttribute(string routeTemplate);
   6: }
   7:  
   8: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
   9: public sealed class HttpPostAttribute : HttpVerbAttribute
  10: {
  11:     public HttpPostAttribute();
  12:     public HttpPostAttribute(string routeTemplate);
  13: }
  14:  
  15: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
  16: public sealed class HttpPutAttribute : HttpVerbAttribute
  17: {
  18:     public HttpPutAttribute();
  19:     public HttpPutAttribute(string routeTemplate);
  20: }
  21:  
  22: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
  23: public sealed class HttpDeleteAttribute : HttpVerbAttribute
  24: {
  25:     public HttpDeleteAttribute();
  26:     public HttpDeleteAttribute(string routeTemplate);
  27: }
  28:  
  29: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
  30: public sealed class HttpPatchAttribute : HttpVerbAttribute
  31: {
  32:     public HttpPatchAttribute();
  33:     public HttpPatchAttribute(string routeTemplate);
  34: }
  35:  
  36: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
  37: public sealed class HttpHeadAttribute : HttpVerbAttribute
  38: {
  39:     public HttpHeadAttribute();
  40:     public HttpHeadAttribute(string routeTemplate);
  41: }
  42:  
  43: [AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=true)]
  44: public sealed class HttpOptionsAttribute : HttpVerbAttribute
  45: {
  46:     public HttpOptionsAttribute();
  47:     public HttpOptionsAttribute(string routeTemplate);
  48: }

在完成了用于进行路由注册的两个HttpRouteInfoProvider特性(AcceptVerbsAttribute和HttpVerbAttribute)之后,我们以实例演示的形式介绍如何利用它们进行针对目标Action方法的路由映射。简单起见,我们只选择使用针对单一HTTP方法的HttpVerbAttribute特性来注册我们需要的路由。

开启针对特性的路由

相对于针对特定的路由(Attribute-based Routing),我们将传统的路由称为基于约定的路由(Convention-based Routing)。在默认的情况下针对特定的路由机制是关闭的,我们需要调用HttpConfiguration具有如下定义的扩展方法MapHttpAttributeRoutes打开这个开关。

   1: public static class HttpConfigurationExtensions
   2: {
   3:     //其他成员
   4:     public static void MapHttpAttributeRoutes(this HttpConfiguration configuration);
   5:     public static void MapHttpAttributeRoutes(this HttpConfiguration configuration, HttpRouteBuilder routeBuilder);
   6: }

如上面的代码片断所示,这个扩展方法具有两个重载,其中一个方法接受一个类型为HttpRouteBuilder的参数。顾名思义,HttpRouteBuilder的作用在于根据应用的HttpRouteInfoProvider特性创建对应的HttpRoute。

基本路由注册

假设有一家专门负责销售影碟的网店,我们为它开发了一套Web API来提供影片的信息,同时供后台应用进行影片资源的维护。简单起见,我们只定义了如下一个表示影片的类型Movie,它仅仅封装了一部电影包含片名、类型、主演、导演、发行日期、对白语言和故事摘要在内的基本信息,属性ID作为它的唯一标识。

   1: public class Movie
   2: {
   3:     public Guid                 Id { get; set; }
   4:     public string               Name { get; set; }
   5:     public IEnumerable<string>  Genres { get; set; }
   6:     public IEnumerable<string>  Starring { get; set; }
   7:     public string               Director { get; set; }
   8:     public DateTime             ReleaseDate { get; set; }
   9:     public string               Language {get; set;}
  10:     public string               Story { get; set; }
  11: }

我们将用于操作影片资源的基本操作定义在如下一个名为MoviesController的HttpController中,5个基本的Action方法上均应用了针对某个HTTP方法的HttpVerbAttribute特性并指定了相应的URL模板。用于根据ID获取对应影片信息的Action方法GetMovieById采用的URL模板为“api/movies/{id}”,另一个Action方法GetMovieByStarring返回由指定的某个演员主演的影片列表,它采用的URL模板为“api/movies/starring/{starring}”。这两个Action方法均仅仅提供针对HTTP-GET请求的唯一支持,URL模板中定义的路由变量(“{id}”和“{starring}”)均映射到目标方法的同名参数上。

   1: public class MoviesController: ApiController
   2: {    
   3:     [HttpGet("api/movies/{id}")]
   4:      public Movie GetMovieById(Guid id);
   5:  
   6:     [HttpGet("api/movies/starring/{starring}")]
   7:      public IEnumerable<Movie> GetMovieByStarring(string starring);
   8:  
   9:     [HttpPost("api/movies")]
  10:      public void Add(Movie movie);
  11:  
  12:     [HttpPut("api/movies")]
  13:      public void Update(Movie movie);
  14:  
  15:     [HttpDelete("api/movies/{id}")]
  16:     public void Delete(string id);
  17: }

其他三个Action方法Add、Update和Delete分别进行添加、修改和删除操作,我们分别在它们上面应用了HttpPostAttribute、HttpPutAttribute和HttpDeleteAttribute来控制它们支持的HTTP方法和指定URL模板。

因为用于注册路由的HttpRouteInfoProvider特性直接应用到目标Action方法上,当ASP.NET Web API根据它们生成相应HttpRoute的时候可以解析出目标HttpController和Action的名称,所以我们使用HttpRouteInfoProvider特性进行路由注册的时候无需指定对应的路由变量(“{controller}”和“{action}”)。

针对定义在MoviesController中支持HTTP-GET请求的两个Action方法GetMovieById和GetMovieByStarring,我们可以直接通过浏览器访问匹配的URL来获取相应的影片信息。如右图所示,我们通过地址“/api/movies/b4e0bc49-568d-402b-acf0-37b65008723f”获取ID为“b4e0bc49-568d-402b-acf0-37b65008723f”的 影片。通过地址“/api/movies/starring/pacino”得到的是由Pacino主要的影片列表。

让URL模板能够尽可能反映资源的层次结构

我们说Web API操作的目标资源具有的层次化结构往往不是指的资源本身具有的物理结构,而只要体现在资源针对不同的调用请求而具有的逻辑结构。就我们的例子来说,目标资源就是一组影片列表而已,其数据本身并不具有复杂的物理结构,其层次化的逻辑结构体现在针对不同条件组合的查询。我们需要针对资源的逻辑结构设计对应的URL,比如:

  • 获取由Pacino主演的剧情片:/api/movies/starring/pacino/drama
  • 获取由Cameron导演的爱情片:/api/movies/director/cameron/romance

针对如上这两种具体的需求,我们可以按照在MoviesController中添加如下两个Action方法。方法GetMovieByStarringAndGenre采用的URL模板分别为“api/movies/starring/{starring}/{genre}”而方法GetMovieByDirectorAndGenre 采用的URL为“api/movies/director/{director}/{genre}”。

   1: public class MoviesController : ApiController
   2: {
   3:     //其他成员
   4:     [HttpGet("api/movies/starring/{starring}/{genre}")]
   5:     public IEnumerable<Movie> GetMovieByStarringAndGenre(string starring, string genre);
   6:  
   7:     [HttpGet("api/movies/director/{director}/{genre}")]
   8:     public IEnumerable<Movie> GetMovieByDirectorAndGenre(string director, string genre);
   9: }

针对注册在这两个Action方法上的路由,我们可以采用匹配的URL获取相应的影片列表。如右图6-2所示,我们直接利用浏览器发送请求来调用上面定义的两个Action方法。我们采用地址“/api/movies/starring/pacino/crime”获取由Pacino主演的犯罪类型的影片列表,而另一个地址“/api/movies/director/brest/drama”则用于获取由Brest导演的剧情片列表。

通过上面的介绍我们知道两种类型的HttpRouteInfoProvider特性(AcceptVerbsAttribute和HttpVerbAttribute)均具有这样的特性:多个同一类型的特性可以同时应用到相同的Action方法上。如果我们按照如下的方式将针对不同条件组合(主演、导演和类型)的查询均定义在同一个FindMovies方法上,然后通过应用多个HttpGetAttribute特性注册多个采用不同URL模板的路由,那么是否也能解决上面这个问题呢?

   1: public class MoviesController : ApiController
   2: {
   3:     //其他成员
   4:     [HttpGet("api/movies/starring/{starring}")]
   5:     [HttpGet("api/movies/starring/{starring}/{genre}")]
   6:     [HttpGet("api/movies/director/{director}/{genre}")]
   7:     public IEnumerable<Movie> FindMovies(string starring, string director, string genre);
   8: }

实际上这个Action方法FindMovies是没有办法按照应用在它上面的HttpGetAttribute特性指定的URL模板进行调用的。如左图所示,我们在浏览器中采用模式匹配的三个URL来调用Web API,但无一例外都得到一个状态为“404, Not Found”的响应,并提示根据请求的URL无法找在目标HttpController中找到一个匹配的Action。

既然我们我们已经明确将相应的路由通过HttpGetAttribute特性注册到Action方法FindMovies上了,但是为何ASP.NET Web API却说根据请求URL(该URL与注册路由的URL模式相匹配)在MoviesController中找不到匹配的Action呢?其实《ASP.NET Web API是如何根据请求选择Action的?》已经回答了这个问题。

在《ASP.NET Web API是如何根据请求选择Action的?》一文中,我们介绍了ASP.NET Web API针对请求选择目标Action会经历4轮筛选,其中第3轮筛选出的候选Action必须满足这样的条件:Action方法中定义的由URL提供的非缺省参数必须存在于当前请求的URL中。对于我们这个例子来说,三个请求采用的URL都不能完整地提供定义在FindMovies方法上的所有三个参数(starring、director和genre)。

那么既然“非缺省参数”才需要由请求的URL来提供,那么如果我们将参数定义成缺省参数是否可以解决这个问题呢?为此我们按照如下的方式将FindMovies方法的三个参数均定义成默认值为空字符串的可缺省参数。

   1: public class MoviesController : ApiController
   2: {
   3:     //其他成员
   4:     [HttpGet("api/movies/starring/{starring}")]
   5:     [HttpGet("api/movies/starring/{starring}/{genre}")]
   6:     [HttpGet("api/movies/director/{director}/{genre}")]
   7:     public IEnumerable<Movie> FindMovies(string starring = "", string director = "", string genre = "");
   8: }

我们再次利用浏览器采用相同的URL来调用定义在MoviesController中的这个Action方法FindMovies,会得到如右图所示的输出结果,可见将Action方法的参数定义成可缺省的参数可以帮助ASP.NET Web API找到对应我们期望的目标参数,我们在项目中也可以利用这样的变成技巧。

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
 

ASP.NET Web API 2.0新特性:Attribute Routing1的更多相关文章

  1. 尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性

    本文首发于<尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性> 概述 .NET开发者们大家好,我是Rector. 几天前(美国时间2 ...

  2. 一个ASP.NET Web API 2.0应用

    在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.N ...

  3. How ASP.NET Web API 2.0 Works?[持续更新中…]

    一.概述 RESTful Web API [Web标准篇]RESTful Web API [设计篇] 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用 二.路由 ...

  4. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

  5. ASP.NET Web API 2 中的特性路由

    ASP.NET MVC 5.1 开始已经支持基于特性的路由(http://attributerouting.net),ASP.NET WEB API 2 同时也支持了这一特性. 启用特性路 由只需要在 ...

  6. ASP.NET Web API 2.0 统一响应格式

    传统实现 在搭建 Web API 服务的时候,针对客户端请求,我们一般都会自定义响应的 JSON 格式,比如: { "Data" : { "Id" : 100, ...

  7. web Servlet 3.0 新特性之web模块化编程,web-fragment.xml编写及打jar包

    web Servlet 3.0 模块化 原本一个web应用的任何配置都需要在web.xml中进行,因此会使得web.xml变得很混乱,而且灵活性差,因此Servlet 3.0可以将每个Servlet. ...

  8. ASP.NET Web Api构建基于REST风格的服务实战系列教程

    使用ASP.NET Web Api构建基于REST风格的服务实战系列教程[十]——使用CacheCow和ETag缓存资源 系列导航地址http://www.cnblogs.com/fzrain/p/3 ...

  9. 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【开篇】【持续更新中。。。】

    最近发现web api很火,园内也有各种大神已经在研究,本人在asp.net官网上看到一个系列教程,原文地址:http://bitoftech.net/2013/11/25/detailed-tuto ...

随机推荐

  1. android--解--它们的定义tabhost(动态添加的选项+用自己的主动性横向滑动标签+手势切换标签页和内容特征)

    在本文中,解决他们自己的定义tabhost实现,并通过代码集成动态加入标签功能.自己主动标签横向滑动功能.和手势标签按功能之间切换. 我完成了这个完美的解决方案一起以下: 1.定义tabwidget布 ...

  2. iOS开发- &quot;duplicate symbol for architecture i386&quot; 解决的方法

    今天整合项目的时候, 遇到了这样一个问题. duplicate symbol _flag in: /Users/apple/Library/Developer/Xcode/DerivedData/bl ...

  3. sql server int 列 NULLIF,isnull 判断是0还是1 ,如果是0就变成1

    SELECT ISNULL(NULLIF(col1,0),1) ISNULL:  第一个表达式 是 null 返回 第二个表达式,否则 返回 第一个 , ISNULL(表达式1,表达式2) if(表达 ...

  4. 点击鼠标获取元素ID

    原文:点击鼠标获取元素ID public partial class Form1 : Form { public Form1() { InitializeComponent(); } private ...

  5. jQuery按回车键执行指定方法

    1.按Enter键执行指定方法: //按回车进入页面 $(function(){ $(document).keydown(function(event){ if (event.keyCode == 1 ...

  6. EasyUI基础知识Draggable(拖累)

    学习前easyui基于解析器,装载机.对他们来说,入门阶段,我们只需要在一个简单的理解.第一阶段,不宜过深后,.接着,根据easyui订购的文件正在研究安排官方网站Draggable插入. Dragg ...

  7. Python系列教程大汇总

    Python初级教程 Python快速教程 (手册) Python基础01 Hello World! Python基础02 基本数据类型 Python基础03 序列 Python基础04 运算 Pyt ...

  8. MVC页面声命周期

    MVC页面声命周期 ASP.Net请求处理机制初步探索之旅 - Part 4 WebForm页面生命周期   开篇:上一篇我们了解了所谓的请求处理管道,在众多的事件中微软开放了19个重要的事件给我们, ...

  9. java之JAVA异常

    异常的分类 1. 编译时被检测异常:只要是Exception和其子类都是,除了特殊子类RuntimeException体系.         此类异常在处理时必须进行声明或进行捕捉         这 ...

  10. MySQL引擎介绍ISAM,MyISAM,HEAP,InnoDB

    MySQL数据库引擎取决于MySQL在安装的时候是如何被编译的.要添加一个新的引擎,就必须重新编译MYSQL. 在缺省情况下,MYSQL支持三个引擎:ISAM.MYISAM和HEAP.另外两种类型IN ...