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















 
 
 
 
 
 
 