前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分。html
它能够很简单:若是你仅仅只须要会用一些简单的路由,如/Home/Index,那么你只须要配置一个默认路由就能简单搞定;web
它能够很神秘:你的url能够变幻无穷,看到一些看似“无厘头”的url,感受很难理解它如何找到匹配的action,例如/api/user/1/detail,这样一个url可让你纠结半天。面试
它能够很晦涩:当面试官提问“请简单分析下MVC路由机制的原理”,你可能事先就准备好了答案,而后噼里啪啦一顿(型如:UrlRoutingModule→Routes→RouteData→RequestContext→Controller),你可能回答很流利,但并不必定能理解这些个对象究竟是啥意思。两年前的面试,博主也这样作过。ajax
博主以为,究竟路由机制在你的印象中处于哪一面,彻底取决于你的求知欲。路由机制博大精深,博主并未彻底理解,但博主是一个好奇心重的人,总以为神秘的东西就得探索个究竟。今天,博主根据本身的理解,分享下WebApi里面路由的原理以及使用,若有考虑不周,欢迎园友们指正。正则表达式
WebApi系列文章算法
在MVC里面,默认路由机制是经过url路径去匹配对应的action方法,好比/Home/GetUser这个url,就表示匹配Home这个Controller下面的GetUser方法,这个很好理解,由于在MVC里面定义了一个默认路由,在App_Start文件夹下面有一个RouteConfig.cs文件api
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Department", action = "Index", id = UrlParameter.Optional } ); } }
url: "{controller}/{action}/{id}"这个定义了咱们url的规则,{controller}/{action}定义了路由的必须参数,{id}是可选参数
跨域
和MVC里面的路由有点不一样,WebApi的默认路由是经过http的方法(get/post/put/delete)去匹配对应的action,也就是说webapi的默认路由并不须要指定action的名称。仍是来看看它的默认路由配置,咱们新建一个Webapi项目,在App_Start文件夹下面自动生成一个WebApiConfig.cs文件:浏览器
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
和MVC相似,routeTemplate: "api/{controller}/{id}"这个定义了路由的模板,api/{controller}是必选参数,{id}是可选参数,那么问题就来了,若是咱们的url不包含action的名称,那么如何找到请求的方法呢?咱们先来简单看一个例子:服务器
public class OrderController : ApiController { [HttpGet] public object GetAll() { return "Success"; } }
咱们经过url来访问
说明请求可以成功。
为何这个请求可以成功呢?那是由于,当咱们访问http://localhost:21528/api/Order这个路径的时候,webapi的路由引擎会自动去匹配"api/{controller}/{id}"这个模板,因而找到了控制器是Order这个,那么问题来了?它是如何定位到GetAll()这个方法的呢?这里就是和MVC不一样的地方,前面说过,Webapi的路由规则是经过http方法去匹配对应的action,那么,咱们经过浏览器访问http://localhost:21528/api/Order这个路径的时候,浏览器默认经过url访问的都是get请求,因而webapi的路由引擎就会去找Order这个控制器里面的get请求的方法,因为没有参数,因此自动匹配到了无参数的get请求→GetAll()方法,因此请求成功!
固然,WebApi也支持MVC里面的路由机制,但RestFul风格的服务要求请求的url里面不能包含action,因此,在WebApi里面是并不提倡使用MVC路由机制的。
这是一个最简单的例子,下面咱们就来详细看看WebApi里面的路由原理以及使用。
上面咱们提到了,新建一个WebApi服务的时候,会自动在WebApiConfig.cs文件里面生成一个默认路由:
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
将MapHttpRoute()方法转到定义能够,它有四个重载方法:
分别来看看各个参数的做用:
public class OrderController : ApiController { [HttpGet] public object GetAll() { return "Success"; } [HttpGet] public object GetById(int id) { return "Success" + id ; } }
咱们经过http://localhost:21528/api/Order/2来访问,获得结果:
咱们再经过http://localhost:21528/api/Order/a来访问,获得结果:
这个是很好理解的,id的值不匹配正则表达式。
而咱们访问http://localhost:21528/api/Order。结果:
居然连GetAll()方法都找不到了。这是为何呢?原来就是这个约束在做怪,正则\d+表示匹配一个或多个数字,因此若是请求的url里面没有传数字,则自动匹配不到。因此,若是须要匹配无参的方法,咱们把约束改为这样: constraints: new { id = @"\d*" } ,这个表示匹配0个或多个数字,再来试试
这样就OK了。
上述说了那么多都是约束id的,其实你也可使用表达式去约束controller、action等等,但通常不经常使用,咱们就不作过多讲解。
上面介绍了这么多,都是关于默认路由原理的介绍。除了默认路由,咱们也能够自定义路由,咱们将WebApiConfig.cs里面改为这样:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 路由 config.MapHttpAttributeRoutes(); //1.默认路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); //2.自定义路由一:匹配到action config.Routes.MapHttpRoute( name: "ActionApi", routeTemplate: "actionapi/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //3.自定义路由二 config.Routes.MapHttpRoute( name: "TestApi", routeTemplate: "testapi/{controller}/{ordertype}/{id}", defaults: new { ordertype="aa", id = RouteParameter.Optional } ); } }
除了默认路由,咱们再加入另外两个自定义路由规则
第一个自定义路由很好理解,和MVC里面的路由机制保持一致,只不过为了区别默认路由,咱们将路由模板的前缀改为了“actionapi”。咱们经过这个自定义的路由也能找到匹配的方法。
好比咱们访问http://localhost:21528/actionapi/Order/GetAll,获得结果:
经过action的名称来匹配很好理解,上面的GetAll()是方法名,webApi会默认它就是action的名称,若是你想要方法名和action的名称不一致,你也能够自定义action的名称,这个能够经过特性ActionName来实现,以下:
[ActionName("TestActionName")] [HttpGet] public object GetById(int id) { return "Success" + id ; }
测试结果:
以前博主演示参数和返回值的时候都是使用的匹配到action的路由。这种用法和MVC里面保持一致,比较好理解,可是WebApi里面并不提倡。
第二个自定义路由第一眼看上去是不太好理解的,不要紧,咱们先来按照它的路由模板规则使用试试。
经过http://localhost:21528/testapi/Order/aa/匹配到GetAll()方法
经过http://localhost:21528/testapi/Order/aa/2匹配到的是GetById()方法
经过http://localhost:21528/testapi/Order/bb/2匹配到的也是GetById()方法。
什么意思呢?也就是说,只要{ordertype}按照路由规则去配置,都能找到对应的方法。这里的{ordertype}有什么用呢?这个要留在下面介绍特性路由的时候来解释。
有了上面的这些理论做为基础,咱们再来分析下WebApi里面路由机制的原理以及路由匹配的过程。因为WebApi的路由机制和MVC有许多的类似性,因此要想理解Webapi的路由机制,有须要搬出来那些asp.net Rounting里面的对象。这个过程有点复杂,博主就根据本身的理解,提提一些主要的过程:
一、WebApi服务启动以后,会执行全局配置文件Global.asax.cs的 protected void Application_Start(){GlobalConfiguration.Configure(WebApiConfig.Register);} 方法,经过参数委托执行WebApiConfig.cs里面的 public static void Register(HttpConfiguration config) 这个方法,将全部配置的路由信息添加到 HttpRouteCollection 对象中(MVC里面多是RoutCollection对象)保存起来。这里的HttpRoutCollection对象的实例名是Routes,这个很重要,后面要用到。
二、当咱们发送请求到WebApi服务器的时候,好比咱们访问http://localhost:21528/api/Order这个url的时候,请求首先仍是会被UrlRoutingModule监听组件截获,而后,将截获的请求在Routes路由集合中匹配到对应的路由模板(若是匹配不到对应的路由模板,则返回404),获得对应的IHttpRoute对象。IHttpRoute对象是Routes集合里面匹配到的一个实体。
三、将IHttpRoute对象交给当前的请求的上下文对象RequestContext处理,根据IHttpRoute对象里面的url匹配到对应的controller,而后再根据http请求的类型和参数找到对应的action。这样一个请求就能找到对应的方法了。
这个过程自己是很是复杂的,为了简化,博主只选择了最主要的几个过程。更详细的路由机制能够参考:http://www.cnblogs.com/wangiqngpei557/p/3379095.html。这文章写得有点深,有兴趣的能够看看。
经过上文路由的过程,咱们知道,一个请求过来以后,路由主要须要经历三个阶段
这点上面已经说了不少了,主要就是路由模板的配置和url的匹配。在此不做过多说明。
若是你反编译路由模块的代码,你会发现控制器的选择主要在IHttpControllerSelector这个接口的SelectController()方法里面处理。
该方法将当前的请求以HttpRequestMessage对象做为参数传入,返回HttpControllerDescriptor对象。这个接口默认由DefaultHttpControllerSelector这个类提供实现
默认实现的方法里面大体的算法机制是:首先在路由字典中找到实际的控制器的名称(好比“Order”),而后在此控制器名称上面加上字符串“Controller”的到请求控制器的全称(好比“OrderController”),最后找到对应的WebApi的Controller,实例化就获得当前请求的控制器对象。
获得了控制器对象以后,Api引擎经过调用IHttpActionSelector这个接口的SelectAction()方法去匹配action。这个过程主要包括:
若是路由模板配置了{action},那么找到对应的action就很简单,若是没有配置action,则会首先匹配请求类型(get/post/put/delete等),而后匹配请求参数,找到对应的action。咱们看个例子,好比,咱们的controller加以下一些方法。
public class OrderController : ApiController { [HttpGet] public IHttpActionResult GetAll() { return Ok<string>("Success"); } [HttpGet] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); } [HttpPost] public HttpResponseMessage PostData(int id) { return Request.CreateResponse(); } [HttpPost] public HttpResponseMessage SavaData(ORDER order) { return Request.CreateResponse(); } [HttpPut] public IHttpActionResult Put(int id) { return Ok(); } [HttpDelete] public IHttpActionResult DeleteById(int id) { return Ok(); } }
匹配action的结果
url | http方法 | 参数 | 结果 |
---|---|---|---|
http://localhost:21528/api/Order | get | none | 匹配GetAll方法 |
http://localhost:21528/api/Order | get | id | 匹配GetById方法 |
http://localhost:21528/api/Order | post | order | 匹配SavaData方法 |
http://localhost:21528/api/Order | put | id | 匹配Put方法 |
http://localhost:21528/api/Order | delete | id | 匹配DeleteById方法 |
WebApi还提供了一个action同时支持多个http方法的请求,使用AcceptVerbs特性去标记。但博主以为实际使用并很少,有兴趣的能够了解下。
[AcceptVerbs("GET", "POST")] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); }
上面说了这么多都是路由的一些全局配置。而且存在问题:
若是http请求的方法相同(好比都是post请求),而且请求的参数也相同。这个时候彷佛就有点不太好办了,这种状况在实际项目中仍是比较多的。好比
public class OrderController : ApiController { //订单排产 [HttpPost] public void OrderProduct([FromBody]string strPostData) { } //订单取消 [HttpPost] public void OrderCancel([FromBody]string strPostData) { } //订单删除 [HttpPost] public void OrderDelete([FromBody]string strPostData) { } }
这个时候若是使用咱们上面讲的Restful风格的路由是解决不了这个问题的。固然,有园友可能就说了,既然这样,咱们在路由模板里面加上“{action}”不就搞定了么!这样确实可行。但仍是那句话,不提倡。咱们来看看如何使用特性路由解决这个问题。
若是要使用特性路由,首先在WebApiConfig.cs的Register方法里面必须先启用特性路由:
public static void Register(HttpConfiguration config) { // 启用Web API特性路由 config.MapHttpAttributeRoutes(); //1.默认路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
通常状况下,当咱们新建一个WebApi项目的时候,会自动在Register方法里面加上这句话。
咱们在OrderController这个控制器里面加这个action
[Route("Order/SaveData")] [HttpPost] public HttpResponseMessage SavaData(ORDER order) { return Request.CreateResponse(); }
而后咱们经过Web里面的Ajax调用
$(function () { $.ajax({ type: 'post', url: 'http://localhost:21528/Order/SaveData', data: { ID: 2, NO:"aaa"}, success: function (data, status) { alert(data); } }); });
获得结果:
固然,有人可能就有疑义了,这个特性路由的做用和“{action}”的做用同样嘛,其实否则,若是这里改为 [Route("Test/AttrRoute")] ,而后请求的url换成http://localhost:21528/Test/AttrRoute,同样能找到对应的action。
特性路由的目的是为了解决咱们公共路由模板引擎解决不了的问题。一个action定义了特性路由以后,就能经过特性路由上面的路由规则找到。
特性路由的规则可使用“{}”占位符动态传递参数,好比咱们有这样一个特性路由
[Route("ordertype/{id}/order")] [HttpGet] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); }
在浏览器里面调用
调用成功。到此,咱们就能看懂本文最开始那个看似“怪异”的路由→/api/user/1/detail这个了。
[Route("api/order/{id:int=3}/ordertype")] [HttpGet] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); }
这里约束可变部分{id}的取值必须是int类型。而且默认值是3.
看看效果
不知足约束条件,则直接返回404。
在正式项目中,同一个控制器的全部的action的全部特性路由标识一个相同的前缀,这种作法并不是必须,但这样可以增长url的可读性。通常的作法是在控制器上面使用特性[RoutePrefix]来标识。
[RoutePrefix("api/order")] public class OrderController : ApiController { [Route("")] [HttpGet] public IHttpActionResult GetAll() { return Ok<string>("Success"); } [Route("{id:int}")] [HttpGet] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); } [Route("postdata")] [HttpPost] public HttpResponseMessage PostData(int id) { return Request.CreateResponse(); } }
那么这个这个控制器的action的时候,都须要/api/order开头,后面接上action特性路由的规则。
经过以上,咱们就能够构造一个Restful风格的WebApi服务。
[RoutePrefix("api/AttrOrder")] public class OrderController : ApiController { [Route("")] [HttpGet] public IHttpActionResult GetAll() { return Ok<string>("Success"); } [Route("{id:int=3}/OrderDetailById")] [HttpGet] public IHttpActionResult GetById(int id) { return Ok<string>("Success" + id ); } [Route("{no}/OrderDetailByNo")] [HttpGet] public IHttpActionResult GetByNO(string no) { return Ok<string>("Success" + no); } [Route("{name}/OrderDetailByName")] [HttpGet] public IHttpActionResult GetByName(string name) { return Ok<string>("Success" + name); } [Route("postdata")] [HttpPost] public HttpResponseMessage PostData(int id) { return Request.CreateResponse(); } [Route("Test/AttrRoute")] [HttpPost] public HttpResponseMessage SavaData(ORDER order) { return Request.CreateResponse(); } }
获得结果
整了这么久终于整完了。若是你以为本文对你有帮助,请帮忙博主推荐,您的支持是博主最大的动力!