前言:從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(); } }
獲得結果
整了這麼久終於整完了。若是你以爲本文對你有幫助,請幫忙博主推薦,您的支持是博主最大的動力!