ASP.NET Routing之“解析URL”功能详解

开发 后端
ASP.NET Routing中的“解析URL”功能可以把一个字符串根据指定的Pattern拆分成键/值对。本文介绍这个功能的简易实现方法。

经常看我博客的人可能会知道,我是一个喜欢搞点小技巧来实现某个功能的人。例如博客的皮肤,自己花了不少时间定义,也是为了效果丰富一些。当然,搞得最多的是从框架或类库内部取出一点小功能来用用,节省自己开发的时间。这其实也是一种复用,尤其是开发一些“扩展”的时候,例如当时尝试为UpdatePanel增加上传功能,虽然最后的结果不是很理想,但是大部分的Hack以及前后端的交互是非常成功的(最大的问题在于跨浏览器实现iframe通信)。而现在也打算总结一次这方面的简单技巧,为以后的文章贡献点引用资源。

ASP.NET Routing中解析URL功能介绍与实现

这次我们想“复用”的内容是ASP.NET URL Routing中“解析URL”的功能。具体一点地说,就是把一个字符串根据指定的Pattern拆分成键/值对的功能。从.NET Reflector反编译System.Web.Routing.dll的结果来看,这部分的解析工作是交由RouteParser和ParsedRoute两个类完成的。这里引用一下相关的使用代码,如果您感兴趣的话,也可以阅读它们完整的实现:

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);  
 
        ...  
    }  
 
    ...  
}  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

从代码中可以看出,RouteParser的作用是将一个Pattern(如"{controller}/{action}/{id}")转化成一个“解析器”,而这个解析器便是ParsedRoute类。在需要拆分一个URL字符串(如"Home/Index/5")的时候,便会调用ParsedRoute类的Match方法,由此得到一个RouteValueDictionary对象,其中包含了Pattern中定义的名称,和一些值的映射关系。

可能您也能够轻易实现这样的功能,不过既然微软已经帮我们做好了,我们也不妨直接使用一下,偶尔用来拆拆字符串也是挺方便的。只可惜RouteParser和ParsedRoute都是由internal修饰的,我们无法直接访问到。那么就用点小技巧吧……说实话,其实您会发现也就这么一回事,“反射”罢了。因此,我们便学着ASP.NET Routing的做法,构建两个类吧:

解析URL的两个类

internal static class RouteParser  
{  
    public static ParsedRoute Parse(string routeUrl) { ... }  
}  
 
internal class ParsedRoute  
{  
    public RouteValueDictionary Match(string virtualPath, RouteValueDictionary defaultValues) { ... }  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

我们目前的做法算是一种Hack,为了保证其可维护性,我会选择与目标类库/框架的接口尽可能完全一致的做法。这么做的好处在于,我可以很轻易地理解正在实现的功能,一旦出现了任何问题,就可以直接去找对应的内部实现,而不用在一堆堆的反射关系中“翱翔”。

接着便可以实现我们需要的效果了。在这里,我使用了FastReflectionLib来加快反射调用的性能。虽然我不是一个追求性能极致的Geek,但是如果有一种几乎不耗费额外代价,就能得到数百倍的性能提升,何乐而不为呢?

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);  
    }  
}  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

两个类其实都是使用反射,从类库中获取合适的MethodInfo,然后交给MethodInvoker去执行。其他的……由于代码过于简单,我都不知道还需要解释什么东西。最后就使用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"]);  
    }  
}  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

说实话,这个方法并没有太多技术含量,由于我们将自己的实现和目标实现完全对应起来,所以我们所要做的,似乎也都是些机械的“映射”功能而已。这就引发了我的一个想法,既然很“机械”,那么为什么不去让它“自动”完成呢?例如,我们完全可以写一个类库,来实现这样的效果:

[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);  
}  
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

通过定义接口和标记,我们可以直接“声明”需要“挖掘”出来的类型是什么。然后自然可以有框架为我们进行匹配:

IRouteParser parser = HackFactory.Create<IRouteParser>();  
IParsedRoute route = parser.Parse("{controller}/{action}/{id}");  
RouteValueDictionary values = route.Match("Home/Index/5"null); 
  • 1.
  • 2.
  • 3.

是不是一下子变得爽快了许多?简单想了想,这样的框架从技术上来说似乎并没有太多困难。

以上就对ASP.NET Routing中的“解析URL”功能进行了介绍。本文来自老赵点滴:《复用类库内部已有功能》

【编辑推荐】

  1. 为ASP.NET MVC应用添加自定义路由
  2. 学习ASP.NET MVC路由的使用方法
  3. 浅析ASP.NET中的URL Rewrite
  4. 浅谈ASP.NET MVC框架
  5. 介绍ASP.NET MVC中的MvcAjaxPanel
责任编辑:yangsai 来源: 老赵点滴
相关推荐

2009-10-26 15:55:43

URL Routing

2009-08-19 09:23:40

ASP.NET Rou

2009-07-21 15:11:14

ASP.NET Rou

2009-07-22 18:08:00

ASP.NET跨页提交

2009-07-28 17:17:19

ASP.NET概述

2009-07-29 16:08:07

ASP和ASP.NET

2009-08-05 14:46:17

ASP.NET url

2009-08-05 13:16:43

ASP.NET URL

2009-07-31 09:39:59

ASP.NET和URL

2009-03-12 10:42:38

RoutingIgnoreRouteASP.NET

2014-08-26 09:22:40

ASP.NET MVCRouting

2009-07-23 13:19:51

2009-08-05 11:14:33

ASP.NET ISA

2009-07-22 16:25:41

ASP.NET AJA

2009-07-24 10:14:22

ASP.NET开发

2009-07-28 16:57:50

ASP.NET Ses

2009-07-27 17:26:39

ASP.NET功能

2009-08-05 10:17:55

ASP.NET TheASP.NET开发技巧

2009-03-09 13:46:31

RoutingWebASP.NET

2009-08-10 13:32:15

ASP.NET TimASP.NET组件设计
点赞
收藏

51CTO技术栈公众号