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