ASP.NET Routing之“解析URL”功能詳解
經(jīng)常看我博客的人可能會(huì)知道,我是一個(gè)喜歡搞點(diǎn)小技巧來(lái)實(shí)現(xiàn)某個(gè)功能的人。例如博客的皮膚,自己花了不少時(shí)間定義,也是為了效果豐富一些。當(dāng)然,搞得最多的是從框架或類庫(kù)內(nèi)部取出一點(diǎn)小功能來(lái)用用,節(jié)省自己開(kāi)發(fā)的時(shí)間。這其實(shí)也是一種復(fù)用,尤其是開(kāi)發(fā)一些“擴(kuò)展”的時(shí)候,例如當(dāng)時(shí)嘗試為UpdatePanel增加上傳功能,雖然最后的結(jié)果不是很理想,但是大部分的Hack以及前后端的交互是非常成功的(最大的問(wèn)題在于跨瀏覽器實(shí)現(xiàn)iframe通信)。而現(xiàn)在也打算總結(jié)一次這方面的簡(jiǎn)單技巧,為以后的文章貢獻(xiàn)點(diǎn)引用資源。
ASP.NET Routing中解析URL功能介紹與實(shí)現(xiàn)
這次我們想“復(fù)用”的內(nèi)容是ASP.NET URL Routing中“解析URL”的功能。具體一點(diǎn)地說(shuō),就是把一個(gè)字符串根據(jù)指定的Pattern拆分成鍵/值對(duì)的功能。從.NET Reflector反編譯System.Web.Routing.dll的結(jié)果來(lái)看,這部分的解析工作是交由RouteParser和ParsedRoute兩個(gè)類完成的。這里引用一下相關(guān)的使用代碼,如果您感興趣的話,也可以閱讀它們完整的實(shí)現(xiàn):
- public class Route
- {
- public string Url
- {
- get { ... }
- set
- {
- this._parsedRoute = RouteParser.Parse(value);
- this._url = value;
- }
- }
- public override RouteData GetRouteData(HttpContextBase httpContext)
- {
- string virtualPath = ...
- RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
- ...
- }
- ...
- }
從代碼中可以看出,RouteParser的作用是將一個(gè)Pattern(如"{controller}/{action}/{id}")轉(zhuǎn)化成一個(gè)“解析器”,而這個(gè)解析器便是ParsedRoute類。在需要拆分一個(gè)URL字符串(如"Home/Index/5")的時(shí)候,便會(huì)調(diào)用ParsedRoute類的Match方法,由此得到一個(gè)RouteValueDictionary對(duì)象,其中包含了Pattern中定義的名稱,和一些值的映射關(guān)系。
可能您也能夠輕易實(shí)現(xiàn)這樣的功能,不過(guò)既然微軟已經(jīng)幫我們做好了,我們也不妨直接使用一下,偶爾用來(lái)拆拆字符串也是挺方便的。只可惜RouteParser和ParsedRoute都是由internal修飾的,我們無(wú)法直接訪問(wèn)到。那么就用點(diǎn)小技巧吧……說(shuō)實(shí)話,其實(shí)您會(huì)發(fā)現(xiàn)也就這么一回事,“反射”罷了。因此,我們便學(xué)著ASP.NET Routing的做法,構(gòu)建兩個(gè)類吧:
解析URL的兩個(gè)類
- internal static class RouteParser
- {
- public static ParsedRoute Parse(string routeUrl) { ... }
- }
- internal class ParsedRoute
- {
- public RouteValueDictionary Match(string virtualPath, RouteValueDictionary defaultValues) { ... }
- }
我們目前的做法算是一種Hack,為了保證其可維護(hù)性,我會(huì)選擇與目標(biāo)類庫(kù)/框架的接口盡可能完全一致的做法。這么做的好處在于,我可以很輕易地理解正在實(shí)現(xiàn)的功能,一旦出現(xiàn)了任何問(wèn)題,就可以直接去找對(duì)應(yīng)的內(nèi)部實(shí)現(xiàn),而不用在一堆堆的反射關(guān)系中“翱翔”。
接著便可以實(shí)現(xiàn)我們需要的效果了。在這里,我使用了FastReflectionLib來(lái)加快反射調(diào)用的性能。雖然我不是一個(gè)追求性能極致的Geek,但是如果有一種幾乎不耗費(fèi)額外代價(jià),就能得到數(shù)百倍的性能提升,何樂(lè)而不為呢?
- internal static class RouteParser
- {
- private static MethodInvoker s_parseInvoker;
- static RouteParser()
- {
- var parserType = typeof(Route).Assembly.GetType("System.Web.Routing.RouteParser");
- var parseMethod = parserType.GetMethod("Parse", BindingFlags.Static | BindingFlags.Public);
- s_parseInvoker = new MethodInvoker(parseMethod);
- }
- public static ParsedRoute Parse(string routeUrl)
- {
- return new ParsedRoute(s_parseInvoker.Invoke(null, routeUrl));
- }
- }
- internal class ParsedRoute
- {
- private static MethodInvoker s_matchInvoker;
- static ParsedRoute()
- {
- var routeType = typeof(Route).Assembly.GetType("System.Web.Routing.ParsedRoute");
- var matchMethod = routeType.GetMethod("Match", BindingFlags.Instance | BindingFlags.Public);
- s_matchInvoker = new MethodInvoker(matchMethod);
- }
- private object m_instance;
- public ParsedRoute(object instance)
- {
- this.m_instance = instance;
- }
- public RouteValueDictionary Match(string virtualPath, RouteValueDictionary defaultValues)
- {
- return (RouteValueDictionary)s_matchInvoker.Invoke(this.m_instance, virtualPath, defaultValues);
- }
- }
兩個(gè)類其實(shí)都是使用反射,從類庫(kù)中獲取合適的MethodInfo,然后交給MethodInvoker去執(zhí)行。其他的……由于代碼過(guò)于簡(jiǎn)單,我都不知道還需要解釋什么東西。最后就使用xUnit測(cè)試一下吧:
解析URL效果測(cè)試
- public class ParseRouteTest
- {
- [Fact]
- public void Basic_Parsing()
- {
- var parsedRoute = RouteParser.Parse("{controller}/{action}/{id}");
- var values = parsedRoute.Match("Home/Index/5", null);
- Assert.Equal("Home", values["controller"]);
- Assert.Equal("Index", values["action"]);
- Assert.Equal("5", values["id"]);
- }
- }
說(shuō)實(shí)話,這個(gè)方法并沒(méi)有太多技術(shù)含量,由于我們將自己的實(shí)現(xiàn)和目標(biāo)實(shí)現(xiàn)完全對(duì)應(yīng)起來(lái),所以我們所要做的,似乎也都是些機(jī)械的“映射”功能而已。這就引發(fā)了我的一個(gè)想法,既然很“機(jī)械”,那么為什么不去讓它“自動(dòng)”完成呢?例如,我們完全可以寫(xiě)一個(gè)類庫(kù),來(lái)實(shí)現(xiàn)這樣的效果:
- [Type("System.Web.Routing.ParsedRoute, ...")]
- interface IParsedRoute
- {
- RouteValueDictionary Match(string virtualPath, RouteValueDictionary defaultValues);
- }
- [Type("System.Web.Routing.RouteParser, ...")]
- interface IRouteParser
- {
- [Static]
- IParsedRoute Parse(string url);
- }
通過(guò)定義接口和標(biāo)記,我們可以直接“聲明”需要“挖掘”出來(lái)的類型是什么。然后自然可以有框架為我們進(jìn)行匹配:
- IRouteParser parser = HackFactory.Create<IRouteParser>();
- IParsedRoute route = parser.Parse("{controller}/{action}/{id}");
- RouteValueDictionary values = route.Match("Home/Index/5", null);
是不是一下子變得爽快了許多?簡(jiǎn)單想了想,這樣的框架從技術(shù)上來(lái)說(shuō)似乎并沒(méi)有太多困難。
以上就對(duì)ASP.NET Routing中的“解析URL”功能進(jìn)行了介紹。本文來(lái)自老趙點(diǎn)滴:《復(fù)用類庫(kù)內(nèi)部已有功能》
【編輯推薦】

















