說明,: 很有用的關(guān)開WebApi的Route 機(jī)制說明文檔 閱讀目錄 正文 前言:從MVC到WebApi,路由機(jī)制一直是伴隨著這些技術(shù)的一個重要組成部分。 它可以很簡單:如果你僅僅只需要會用一些簡單的路由,如/Home/Index,那么你只需要配置一個默認(rèn)路由就能簡單搞定; 它可以很神秘:你的url可以千變?nèi)f化,看到一些看似“無厘頭”的url,感覺很難理解它如何找到匹配的action,例如/api/user/1/detail,這樣一個url可以讓你糾結(jié)半天。 它可以很晦澀:當(dāng)面試官提問“請簡單分析下MVC路由機(jī)制的原理”,你可能事先就準(zhǔn)備好了答案,然后噼里啪啦一頓(型如:UrlRoutingModule→Routes→RouteData→RequestContext→Controller),你可能回答很流利,但并不一定能理解這些個對象到底是啥意思。兩年前的面試,博主也這樣做過。 博主覺得,究竟路由機(jī)制在你的印象中處于哪一面,完全取決于你的求知欲。路由機(jī)制博大精深,博主并未完全理解,但博主是一個好奇心重的人,總覺得神秘的東西就得探索個究竟。今天,博主根據(jù)自己的理解,分享下WebApi里面路由的原理以及使用,如有考慮不周,歡迎園友們指正。 WebApi系列文章
一、MVC和WebApi路由機(jī)制比較1、MVC里面的路由在MVC里面,默認(rèn)路由機(jī)制是通過url路徑去匹配對應(yīng)的action方法,比如/Home/GetUser這個url,就表示匹配Home這個Controller下面的GetUser方法,這個很好理解,因?yàn)樵贛VC里面定義了一個默認(rèn)路由,在App_Start文件夾下面有一個RouteConfig.cs文件 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的規(guī)則,{controller}/{action}定義了路由的必須參數(shù),{id}是可選參數(shù) 2、WebApi里面的路由和MVC里面的路由有點(diǎn)不同,WebApi的默認(rèn)路由是通過http的方法(get/post/put/delete)去匹配對應(yīng)的action,也就是說webapi的默認(rèn)路由并不需要指定action的名稱。還是來看看它的默認(rèn)路由配置,我們新建一個Webapi項(xiàng)目,在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}是必選參數(shù),{id}是可選參數(shù),那么問題就來了,如果我們的url不包含action的名稱,那么如何找到請求的方法呢?我們先來簡單看一個例子: public class OrderController : ApiController { [HttpGet]public object GetAll() {return "Success"; } } 我們通過url來訪問 說明請求能夠成功。 為什么這個請求能夠成功呢?那是因?yàn)?,?dāng)我們訪問http://localhost:21528/api/Order這個路徑的時候,webapi的路由引擎會自動去匹配"api/{controller}/{id}"這個模板,于是找到了控制器是Order這個,那么問題來了?它是如何定位到GetAll()這個方法的呢?這里就是和MVC不同的地方,前面說過,Webapi的路由規(guī)則是通過http方法去匹配對應(yīng)的action,那么,我們通過瀏覽器訪問http://localhost:21528/api/Order這個路徑的時候,瀏覽器默認(rèn)通過url訪問的都是get請求,于是webapi的路由引擎就會去找Order這個控制器里面的get請求的方法,由于沒有參數(shù),所以自動匹配到了無參數(shù)的get請求→GetAll()方法,所以請求成功! 當(dāng)然,WebApi也支持MVC里面的路由機(jī)制,但RestFul風(fēng)格的服務(wù)要求請求的url里面不能包含action,所以,在WebApi里面是并不提倡使用MVC路由機(jī)制的。 這是一個最簡單的例子,下面我們就來詳細(xì)看看WebApi里面的路由原理以及使用。 二、WebApi路由基礎(chǔ)1、默認(rèn)路由上面我們提到了,新建一個WebApi服務(wù)的時候,會自動在WebApiConfig.cs文件里面生成一個默認(rèn)路由: config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); 將MapHttpRoute()方法轉(zhuǎn)到定義可以,它有四個重載方法: 分別來看看各個參數(shù)的作用:
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來訪問,得到結(jié)果: 我們再通過http://localhost:21528/api/Order/a來訪問,得到結(jié)果: 這個是很好理解的,id的值不匹配正則表達(dá)式。 而我們訪問http://localhost:21528/api/Order。結(jié)果: 竟然連GetAll()方法都找不到了。這是為什么呢?原來就是這個約束在作怪,正則\d+表示匹配一個或多個數(shù)字,所以如果請求的url里面沒有傳數(shù)字,則自動匹配不到。所以,如果需要匹配無參的方法,我們把約束改成這樣: constraints: new { id = @"\d*" } ,這個表示匹配0個或多個數(shù)字,再來試試 這樣就OK了。 上述說了那么多都是約束id的,其實(shí)你也可以使用表達(dá)式去約束controller、action等等,但一般不常用,我們就不做過多講解。 2、自定義路由上面介紹了這么多,都是關(guān)于默認(rèn)路由原理的介紹。除了默認(rèn)路由,我們也可以自定義路由,我們將WebApiConfig.cs里面改成這樣: public static class WebApiConfig {public static void Register(HttpConfiguration config) {// Web API 路由 config.MapHttpAttributeRoutes();//1.默認(rèn)路由 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 } ); } } 除了默認(rèn)路由,我們再加入另外兩個自定義路由規(guī)則 2.1、自定義路由一:匹配到action第一個自定義路由很好理解,和MVC里面的路由機(jī)制保持一致,只不過為了區(qū)別默認(rèn)路由,我們將路由模板的前綴改成了“actionapi”。我們通過這個自定義的路由也能找到匹配的方法。 比如我們訪問http://localhost:21528/actionapi/Order/GetAll,得到結(jié)果: 通過action的名稱來匹配很好理解,上面的GetAll()是方法名,webApi會默認(rèn)它就是action的名稱,如果你想要方法名和action的名稱不一致,你也可以自定義action的名稱,這個可以通過特性ActionName來實(shí)現(xiàn),如下: 測試結(jié)果: 之前博主演示參數(shù)和返回值的時候都是使用的匹配到action的路由。這種用法和MVC里面保持一致,比較好理解,但是WebApi里面并不提倡。 2.2、自定義路由二第二個自定義路由第一眼看上去是不太好理解的,沒關(guān)系,我們先來按照它的路由模板規(guī)則使用試試。 通過http://localhost:21528/testapi/Order/aa/匹配到GetAll()方法 通過http://localhost:21528/testapi/Order/aa/2匹配到的是GetById()方法 通過http://localhost:21528/testapi/Order/bb/2匹配到的也是GetById()方法。 什么意思呢?也就是說,只要{ordertype}按照路由規(guī)則去配置,都能找到對應(yīng)的方法。這里的{ordertype}有什么用呢?這個要留在下面介紹特性路由的時候來解釋。 3、路由原理有了上面的這些理論作為基礎(chǔ),我們再來分析下WebApi里面路由機(jī)制的原理以及路由匹配的過程。由于WebApi的路由機(jī)制和MVC有許多的相似性,所以要想理解Webapi的路由機(jī)制,有需要搬出來那些asp.net Rounting里面的對象。這個過程有點(diǎn)復(fù)雜,博主就根據(jù)自己的理解,提提一些主要的過程: 1、WebApi服務(wù)啟動之后,會執(zhí)行全局配置文件Global.asax.cs的 protected void Application_Start(){GlobalConfiguration.Configure(WebApiConfig.Register);} 方法,通過參數(shù)委托執(zhí)行WebApiConfig.cs里面的 public static void Register(HttpConfiguration config) 這個方法,將所有配置的路由信息添加到 HttpRouteCollection 對象中(MVC里面可能是RoutCollection對象)保存起來。這里的HttpRoutCollection對象的實(shí)例名是Routes,這個很重要,后面要用到。 2、當(dāng)我們發(fā)送請求到WebApi服務(wù)器的時候,比如我們訪問http://localhost:21528/api/Order這個url的時候,請求首先還是會被UrlRoutingModule監(jiān)聽組件截獲,然后,將截獲的請求在Routes路由集合中匹配到對應(yīng)的路由模板(如果匹配不到對應(yīng)的路由模板,則返回404),得到對應(yīng)的IHttpRoute對象。IHttpRoute對象是Routes集合里面匹配到的一個實(shí)體。 3、將IHttpRoute對象交給當(dāng)前的請求的上下文對象RequestContext處理,根據(jù)IHttpRoute對象里面的url匹配到對應(yīng)的controller,然后再根據(jù)http請求的類型和參數(shù)找到對應(yīng)的action。這樣一個請求就能找到對應(yīng)的方法了。 這個過程本身是非常復(fù)雜的,為了簡化,博主只選擇了最主要的幾個過程。更詳細(xì)的路由機(jī)制可以參考:http://www.cnblogs.com/wangiqngpei557/p/3379095.html。這文章寫得有點(diǎn)深,有興趣的可以看看。 三、WebApi路由過程通過上文路由的過程,我們知道,一個請求過來之后,路由主要需要經(jīng)歷三個階段
1、根據(jù)請求的url匹配路由模板這點(diǎn)上面已經(jīng)說了很多了,主要就是路由模板的配置和url的匹配。在此不作過多說明。 2、找到控制器如果你反編譯路由模塊的代碼,你會發(fā)現(xiàn)控制器的選擇主要在IHttpControllerSelector這個接口的SelectController()方法里面處理。 該方法將當(dāng)前的請求以HttpRequestMessage對象作為參數(shù)傳入,返回HttpControllerDescriptor對象。這個接口默認(rèn)由DefaultHttpControllerSelector這個類提供實(shí)現(xiàn) 默認(rèn)實(shí)現(xiàn)的方法里面大致的算法機(jī)制是:首先在路由字典中找到實(shí)際的控制器的名稱(比如“Order”),然后在此控制器名稱上面加上字符串“Controller”的到請求控制器的全稱(比如“OrderController”),最后找到對應(yīng)的WebApi的Controller,實(shí)例化就得到當(dāng)前請求的控制器對象。 3、找到action得到了控制器對象之后,Api引擎通過調(diào)用IHttpActionSelector這個接口的SelectAction()方法去匹配action。這個過程主要包括:
如果路由模板配置了{(lán)action},那么找到對應(yīng)的action就很簡單,如果沒有配置action,則會首先匹配請求類型(get/post/put/delete等),然后匹配請求參數(shù),找到對應(yīng)的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的結(jié)果
WebApi還提供了一個action同時支持多個http方法的請求,使用AcceptVerbs特性去標(biāo)記。但博主覺得實(shí)際使用并不多,有興趣的可以了解下。 [AcceptVerbs("GET", "POST")]public IHttpActionResult GetById(int id) {return Ok<string>("Success" + id ); } 四、WebApi特性路由上面說了這么多都是路由的一些全局配置。并且存在問題: 如果http請求的方法相同(比如都是post請求),并且請求的參數(shù)也相同。這個時候似乎就有點(diǎn)不太好辦了,這種情況在實(shí)際項(xiàng)目中還是比較多的。比如 public class OrderController : ApiController {//訂單排產(chǎn) [HttpPost]public void OrderProduct([FromBody]string strPostData) { }//訂單取消 [HttpPost]public void OrderCancel([FromBody]string strPostData) { }//訂單刪除 [HttpPost]public void OrderDelete([FromBody]string strPostData) { } } 這個時候如果使用我們上面講的Restful風(fēng)格的路由是解決不了這個問題的。當(dāng)然,有園友可能就說了,既然這樣,我們在路由模板里面加上“{action}”不就搞定了么!這樣確實(shí)可行。但還是那句話,不提倡。我們來看看如何使用特性路由解決這個問題。 1、啟動特性路由如果要使用特性路由,首先在WebApiConfig.cs的Register方法里面必須先啟用特性路由: public static void Register(HttpConfiguration config) {// 啟用Web API特性路由 config.MapHttpAttributeRoutes();//1.默認(rèn)路由 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } 一般情況下,當(dāng)我們新建一個WebApi項(xiàng)目的時候,會自動在Register方法里面加上這句話。 2、最簡單的特性路由我們在OrderController這個控制器里面加這個action [Route("Order/SaveData")] [HttpPost]public HttpResponseMessage SavaData(ORDER order) {return Request.CreateResponse(); } 然后我們通過Web里面的Ajax調(diào)用 $(function () { $.ajax({ type: 'post', url: 'http://localhost:21528/Order/SaveData', data: { ID: 2, NO:"aaa"}, success: function (data, status) { alert(data); } }); }); 得到結(jié)果: 當(dāng)然,有人可能就有疑義了,這個特性路由的作用和“{action}”的作用一樣嘛,其實(shí)不然,如果這里改成 [Route("Test/AttrRoute")] ,然后請求的url換成http://localhost:21528/Test/AttrRoute,一樣能找到對應(yīng)的action。 特性路由的目的是為了解決我們公共路由模板引擎解決不了的問題。一個action定義了特性路由之后,就能通過特性路由上面的路由規(guī)則找到。 3、帶參數(shù)的特性路由特性路由的規(guī)則可以使用“{}”占位符動態(tài)傳遞參數(shù),比如我們有這樣一個特性路由 [Route("ordertype/{id}/order")] [HttpGet]public IHttpActionResult GetById(int id) {return Ok<string>("Success" + id ); } 在瀏覽器里面調(diào)用 調(diào)用成功。到此,我們就能看懂本文最開始那個看似“怪異”的路由→/api/user/1/detail這個了。 4、參數(shù)的約束和默認(rèn)值[Route("api/order/{id:int=3}/ordertype")] [HttpGet]public IHttpActionResult GetById(int id) {return Ok<string>("Success" + id ); } 這里約束可變部分{id}的取值必須是int類型。并且默認(rèn)值是3. 看看效果 不滿足約束條件,則直接返回404。 5、路由前綴在正式項(xiàng)目中,同一個控制器的所有的action的所有特性路由標(biāo)識一個相同的前綴,這種做法并非必須,但這樣能夠增加url的可讀性。一般的做法是在控制器上面使用特性[RoutePrefix]來標(biāo)識。 [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特性路由的規(guī)則。 五、第一個Restful風(fēng)格的WebApi服務(wù)通過以上,我們就可以構(gòu)造一個Restful風(fēng)格的WebApi服務(wù)。 [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(); } } 得到結(jié)果 六、總結(jié)整了這么久終于整完了。如果你覺得本文對你有幫助,請幫忙博主推薦,您的支持是博主最大的動力! |
|