Hello World
Spiga

关于ASP.NET Routing的几点内容

2009-09-30 10:44 by 老赵, 13984 visits

好吧,我承认这个标题有些八股。

在之前的文章中,有一些朋友会问我一些关于ASP.NET Routing的内容。这个组件的重要性越来越大,ASP.NET MVC,ASP.NET Dynamic Data都用到了ASP.NET Routing。事实上,在ASP.NET 4.0中还会出现对ASP.NET WebForms的支持。可惜的是,目前对于ASP.NET Routing的文档和描述内容都很少。因此,有的时候一些朋友可能无法理解我一些扩展的设计思路。现在我打算详细解释一下有关ASP.NET Routing中最常见的几个问题。

ASP.NET Routing的作用究竟是什么

ASP.NET Routing的作用有两个:

  1. 从请求中捕获数据。
  2. 从数据生成虚拟路径。

可见,ASP.NET Routing的功能是双向的。值得注意的是,这两个操作严格说来并不对称。因为第1点是从“请求”中捕获数据,而数据源并不一定是一个URL,可能是Server Variables,可能是Header;与此对应,从数据生成的虚拟路径,则一定是生成一个URL,一个字符串。

平时使用ASP.NET Routing的时候,我们会大量利用ASP.NET Routing组件中的Route类,它的作用是从Virtual Path中获取数据,并提供一些如默认值,约束等高级功能。但是,对于ASP.NET Routing组件,或者说它的“引擎”使用的是一个抽象的类型“RouteBase”。而Route只是RouteBase的一个实现罢了。

在之前的文章中,我曾经提出过其他一些扩展,如FormatRouteDomainRoute

为什么Route不使用正则表达式

ASP.NET Routing中自带的Route类会根据我们指定的“路径模板”,从请求的虚拟路径中捕获数据。而这个“模板”是类似这种形式的:

{controller}/{action}/{id}

于是根据当前请求的虚拟路径,便可从中捕获出controller,action和id三个值了。不过有朋友说,为什么不使用正则表达式来捕获数据呢?例如:

(?<controller>\w+)/(?<action>\w+)/(?<id>\d+)

使用命名捕获组的正则表达式来虚拟路径,自然也可以得到controller,action和id的值了。此外,使用正则表达式的另一个好处是可以严格约束路径中的字符,例如上面的正则表达式将controller和action限制为单词字符,而将id限制为数字,这样不满足这种形式的路径便无法获得匹配了。不过问题出在哪里呢?

这里的问题便是:ASP.NET Routing的工作是双向的,而如果使用正则表达式,捕获数据自然没话说,但是从正则表达式来生成一个字符串就不容易了,由于正则表达式的匹配形式非常广,很多时候甚至根本无法逆向得到一个字符串。因此现在,如果您要对某个值进行约束,只能为Route对象提供一个约束条件了:

new { controller = "\w+", action="\w+", id="\d+" }

当然,如果您需要的话,可以实现一个正则表达式匹配规则的子集,使生成字符串的工作变得可行,那么也是没有问题的。

ASP.NET Routing工作流程

ASP.NET Routing的工作有两方面,处理请求和生成虚拟路径。处理请求时的流程已经在之前的文章里详细谈过了,这里再谈一下ASP.NET Routing生成虚拟路径的做法。

ASP.NET Routing在生成虚拟路径时还是使用RouteCollection类型上的方法,自然我们是像RouteTable.Routes属性中注册的Routing规则,那么自然也是从RouteTable.Routes属性中获取虚拟路径。RouteCollection类型中有一个GetVirtualPath方法,返回一个VirtualPathData对象,其中包含一个VirtualPath属性,便是最终得到的虚拟路径。

GetVirtualPath方法有两个重载,第一个是:

public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)

RequestContext对象是当前请求的上下文,而RouteValueDictionary包含的则是用于构造虚拟路径的数据。与处理请求的方式相同,RouteCollection类型的GetVirtualPath方法也会依次调用每个RouteBase对象的GetVirtualPath方法,并返回第一个不为null的结果(事实上还会经过处理,且看下文)。如果一个RouteBase对象都无法匹配当前的提供的数据,则RouteCollection的GetVirtualPath返回null。

GetVirtualPath方法的另一个重载则会提供一个name参数:

public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values)

name参数的作用是“指定”一个Routing规则,也就是一个特定的RouteBase对象。如果这个RouteBase对象返回null,则GetVirtualPath方法直接返回null。指定name的优势在于代码可读性高(开发人员可以直接找到对应的Routing策略)、性能有一定优势(无需遍历每个Routing规则),以及Routing规则之间不会产生冲突。

ASP.NET Routing支持域名吗?

不支持,从设计上就不支持。

这一点从RouteCollection的GetVirtualPath方法实现中可以看到,这个方法在遍历每个RouteBase对象并得到第一个不为null的结果后,它不是立即返回,还要使用GetUrlWithApplicationPath方法进行处理:

private static string GetUrlWithApplicationPath(RequestContext requestContext, string url)
{
    string str = requestContext.HttpContext.Request.ApplicationPath ?? string.Empty;
    if (!str.EndsWith("/", StringComparison.OrdinalIgnoreCase))
    {
        str = str + "/";
    }

    return requestContext.HttpContext.Response.ApplyAppPathModifier(str + url);
}

url参数的含义是“虚拟路径”,GetUrlWithApplicationPath方法的作用则是为其加上“应用程序目录”。例如,我们一个站点可能不是部署在“/”下,而是部署在“/MvcApp”这个“子站点”中。这样,虽然RouteBase的GetVirtualPath返回的是虚拟路径“Home/Index/5”,但是RouteCollection的GetVirtualPath方法在使用GetUrlWithApplicationPath处理之后,最终返回的路径便是/MvcApp/Home/Index/5这样的字符串了。

因此可以这么说,ASP.NET Routing从设计上就不支持域名的概念,虽然我们可以扩展RouteBase对象,但是我们无法扩展RouteCollection的GetVirtualPath方法。换句话说,即使我们的DomainRoute可以返回类似“http://www.cnblogs.com/Home”这样的URL,被RouteCollection的GetVirtualPath方法一鼓捣又会在之前加一条斜杠——因此,在MvcPatch的MvcPatch.Routing项目中,我为RouteCollection提供了一个名为GetVirtualPathEx的扩展方法,如果它发现URL以http或https开头,则不会拼接上应用程序目录。至于其他情况下,则和原来的GetVirtualPath方法行为一致。

如果您要使用DomainRoute,那么请在需要的使用GetVirtualPathEx而不是原来的GetVirtualPath方法。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

19 条回复

  1. Leejor.
    *.*.*.*
    链接

    Leejor. 2009-09-30 11:01:00

    SF拿下,看完。

  2. 老赵也是个极顾面子的人![未注册用户]
    *.*.*.*
    链接

    老赵也是个极顾面子的人![未注册用户] 2009-09-30 11:09:00

    被一个无知的管理员放入“候选区”就再不愿接贴了。

  3. 老赵
    admin
    链接

    老赵 2009-09-30 11:13:00

    @老赵也是个极顾面子的人!
    你在说什么?我的文章从没“被”放入候选区过,要放也是我自己主动的。

  4. 老赵的记性还真叫差![未注册用户]
    *.*.*.*
    链接

    老赵的记性还真叫差![未注册用户] 2009-09-30 11:28:00

    http://www.cnblogs.com/JeffreyZhao/archive/2009/09/27/recursive-lambda-expressions-benchmark.html

  5. 老赵
    admin
    链接

    老赵 2009-09-30 11:29:00

    @老赵的记性还真叫差!
    兄弟啊,这是我自己放到候选区去的。
    说了都是我故意放的,没有“被”放入的。

  6. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-09-30 11:40:00

    先收藏,回去慢慢看。

  7. dark
    *.*.*.*
    链接

    dark 2009-09-30 12:08:00

    呼呼~~~ 路过 看看~~

  8. GWPBrian
    *.*.*.*
    链接

    GWPBrian 2009-09-30 12:25:00

    不错,学习了

  9. XinXin_Shine
    *.*.*.*
    链接

    XinXin_Shine 2009-09-30 12:42:00

    ASP.NET MVC Preview版本的变化好大,现在学习ASP.NET MVC 2.0感觉用法大大改变,人家说只要思想好语言是没有国界的,感觉自己总活在现在,明天也是活在明天的现在,进步应该从哪些出发呢?

  10. 老赵
    admin
    链接

    老赵 2009-09-30 13:02:00

    @XinXin_Shine
    我不知道啊,2.0用法变很多吗?

  11. 用心生活
    *.*.*.*
    链接

    用心生活 2009-09-30 13:21:00

    [我为RouteCollection提供了一个名为GetVirtualPathEx的扩展方法,如果它发现URL以http或https开头,则不会拼接上应用程序目录。至于其他情况下,则和原来的GetVirtualPath方法行为一致。]
    override一个新方法?

  12. 老赵
    admin
    链接

    老赵 2009-09-30 13:24:00

    @用心生活
    “扩展方法”,没法override。

  13. 王德水
    *.*.*.*
    链接

    王德水 2009-09-30 14:51:00

    ASP.NET MVC 2.0出来了?还真快

  14. XinXin_Shine
    *.*.*.*
    链接

    XinXin_Shine 2009-10-05 15:51:00

    @Jeffrey Zhao

    Jeffrey Zhao:
    @XinXin_Shine
    我不知道啊,2.0用法变很多吗?


    语法我觉得变化很大,用您教程的例子基本上在2.0上面是不可用的,我举两个最简单的例子吧!
    在2.0中已经没有:ActionLink<>这个语法
    也没有RenderView了.

  15. 老赵
    admin
    链接

    老赵 2009-10-05 16:20:00

    @XinXin_Shine
    这种用法是ctp和beta的内容,在1.0 RTM中就没了。
    ActionLink<>在1.0 RTM也没有,只能在Futures里看到。

  16. 殷敏峰
    *.*.*.*
    链接

    殷敏峰 2009-10-07 18:52:00

    ....
    partial void OnValidate(ChangeAction action)
    {
    if(!Isvalid)
    {
    throw new applicationException(".....");
    }
    }
    ....

    NERDINNER实例中的一段代码。F5时,提示:“未找到用于实现分部方法‘BTDinner20091001.Models.Dinner.OnValidate(System.Data.Linq.ChangeAction)’的定义声明”。

    有救吗?还要声明什么呢?给个具体答案啊

  17. Marlboro
    *.*.*.*
    链接

    Marlboro 2009-10-15 14:46:00

    XinXin_Shine:
    @Jeffrey Zhao

    Jeffrey Zhao:
    @XinXin_Shine
    我不知道啊,2.0用法变很多吗?


    语法我觉得变化很大,用您教程的例子基本上在2.0上面是不可用的,我举两个最简单的例子吧!
    在2.0中已经没有:ActionLink<>这个语法
    也没有RenderView了.



    原来如此,刚看了视频跟我建的WEBAPP里面的代码不一样..

  18. userbibi
    58.210.35.*
    链接

    userbibi 2011-08-03 13:53:33

    是不是这样哪。。什么才是技术,心态呀。。.net的步伐跟不上,也是听了老赵的mvc的教程,还没看完了,怎么又更新了。。没劲、.net附属弄弄吧,不准备跟进了,要用的时候再补一下吧。专注java/c。。

  19. 学.net三个月了
    117.79.232.*
    链接

    学.net三个月了 2014-04-22 16:14:30

    源码链接不存在了呀,想参考一下

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我