Hello World
Spiga

支持DomainRoute的URL构造辅助方法

2009-08-26 12:18 by 老赵, 4670 visits

上一篇文章中我们构造了DomainRoute类,这是一个将URL Routing扩展至域名的Route组件,于是现在我们便可以轻易地从一个URL的Domain部分中捕获数据并在程序中使用。不过作为URL Routing的另一个重要部分,在URL构建方面,我们还需给DomainRoute补充额外的支持。

我想再次强调一下,URL Routing的功能并不只是从URL中捕获数据,它还有一个作用便是根据数据组装出一个URL。简单地说,URL Routing的功能是“双向”的。这也是为什么默认的Route组件会使用{controller}/{action}这样的简单模式,而不是常用的成熟的正则表达式,这就是因为正则表达式能够根据模式来匹配出值来,但是无法根据“值”反过来构建一个字符串。

在编写ASP.NET MVC的视图时,我们需要在HTML中填充链接的URL。在最初的时刻,我们会直接放上“约定”好的字符串,但是更好的方式显然是使用辅助方法。记得在搞URL Rewrite时,常常有朋友问我“页面中填写的URL怎么变成‘漂亮’的样子”,对此我只能说“没办法,直接写吧”,接着自然是一阵大改。而通过辅助方法来构造URL,由于我们只要向URL Routing提供数据,而Route会自动根据配置构造出一个可供自己识别的URL。如果我们想改变URL样式,只要在一个地方改变URL Routing的配置即可,页面中所有的URL便会同时改变了。

我认为,这也是遵守DRY原则的经典示例之一。

在ASP.NET MVC中已经自带了一部分构造URL所使用的辅助方法,例如在UrlHelper类中已经包含了Action方法(以及许多重载)。但是这些个方法并不适合FormatRoute。因为在这些实现最终都使用了RouteCollection的GetVirtualPath方法,它会把Route所得到的URL作为“虚拟路径”对待,这意味着FormatRoute返回了http://www.cnblogs.com/JeffreyZhao/这样的URL之后,也会当作是一个普通的Path,最终在页面上便会出现“/http:/www.cnblogs.com/JeffreyZhao/”这样的URL,这显然是错误的。

这样看来,难道ASP.NET Routing本身是“不打算”支持域名的吗?似乎我们又不仅仅是在“扩展”,又出现“Hack”的意味了。

不过知道了错误原因之后,修改起来就非常容易了。例如,我们可以为UrlHelper补充一个扩展方法叫做ActionEx,实现起来也就几行代码:

public static string ActionEx(this UrlHelper helper, string action, object routeValues)
{
    var values = routeValues == null ?
        new RouteValueDictionary() : 
        new RouteValueDictionary(routeValues);
    values.Add("action", action);
    values.Add("controller", helper.RequestContext.RouteData.Values["controller"]);

    var pathData = helper.RouteCollection.GetPath(helper.RequestContext, values);
    var url = pathData.VirtualPath;
    return IsAbsolute(url) ? url : "/" + url;
}

private static VirtualPathData GetPath(
    this RouteCollection routes,
    RequestContext requestContext,
    RouteValueDictionary values)
{
    foreach (RouteBase r in routes)
    {
        VirtualPathData pathData = r.GetVirtualPath(requestContext, values);
        if (pathData != null)
        {
            return pathData;
        }
    }

    throw new ArgumentException("Invalid values for building URL.");
}

private static bool IsAbsolute(string url)
{
    return
        url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) ||
        url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase);
}

要做的事情还是分三步走:

  1. 收集route values
  2. 从route配置中获得URL
  3. 处理URL并返回

根据FormatRoute的逻辑,返回的URL可能有两种情况:

  • 如果目标地址与当前请求的域名相同,则地址为一个相对路径,如“Home/Index/5”。
  • 如果目标地址与当前请求的域名不同,则地址为一个绝对路径,如“http://space.cnblogs.com/Home/Index/5”。

两种地址的处理方式自然不同,而我们目前的处理方式是最简单的:在相对路径之前加一个斜杠“/”。很明显,这里做了一个“假设”,那就是我们的ASP.NET MVC应用程序是部署在域名的根目录下的(似乎DomainRoute本身也有这样的要求)。如果您的应用程序部署在一个虚拟目录下,这部分逻辑自然是需要修改的。不过作为大部分应用来说,现在的功能应该已经足够使用了。

试验一番吧。例如我们使用这样的Route配置:

var defaults = new RouteValueDictionary(new { controller = "Home", action = "Index", id = "" });
var route =
    new Route("{controller}/{action}/{id}", defaults, new MvcRouteHandler())
    .WithDomain("http://{area}.{*domain}");
routes.Add("Default", route);

然后请求http://www.jeffz-test.com/Home/Index,并在相应视图中写下这样的代码:

<a href="<%= Url.ActionEx("List", null) %>">List</a>
<a href="<%= Url.ActionEx("LogIn", new { id = 10, area = "account" })%>">Accout LogIn</a>

于是在页面上就会生成这样的HTML:

<a href="/Home/List">List</a> 
<a href="http://account.jeffz-test.com/Home/LogIn/10">Accout LogIn</a>

看上去不错吧?

ASP.NET MVC中的其他与构造URL功能有关的辅助方法,如ActionLink,其实也都是相同的原理。如果您需要的话,也不妨自己实现一个对应的ActionLinkEx方法。

不过,根据尽可能强类型的原则,我们应该使用的是MvcFutures中定义的基于表达式树的辅助方法。不过MvcFutures里的这些方法有些问题,如最常见的“视ActionNameAttribute于无物”。在使用过程中我也遇到了其他一些问题,下次我们再来改造这些辅助方法。

在条件充分的时候,使用表达式树来表示URL构造,可以说有百利而无一害。

Creative Commons License

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

Add your comment

16 条回复

  1. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-08-26 12:38:00

    沙发

  2. 老赵
    admin
    链接

    老赵 2009-08-26 13:27:00

    某位兄弟您的名字太不雅,删了,建议换名字重发。

  3. 巨大与红肿[未注册用户]
    *.*.*.*
    链接

    巨大与红肿[未注册用户] 2009-08-26 14:06:00

    一巴掌呼死你

  4. 老赵
    admin
    链接

    老赵 2009-08-26 14:10:00

    @巨大与红肿
    既然您喜欢这个名字那么我也就不强求了。

  5. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-26 14:37:00

    博客园为什么有这么多匿名用户呢?不敢真面目见人吗?

  6. Gnie
    *.*.*.*
    链接

    Gnie 2009-08-26 14:49:00

    麒麟.NET:博客园为什么有这么多匿名用户呢?不敢真面目见人吗?


    是啊,不理解

  7. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-08-26 16:52:00

    没有一个针对文章的评论。。

    看来asp.net mvc还不够普及啊

  8. Jaryleely
    *.*.*.*
    链接

    Jaryleely 2009-08-26 22:46:00

    赵老师,看.net framework框架程序设计这本书,应该重点看哪些部分,里面有些东西太深奥了。。都有些搞不懂。。请赵老师给些建议?

  9. Jaryleely
    *.*.*.*
    链接

    Jaryleely 2009-08-26 22:47:00

    谢谢了!

  10. 老赵
    admin
    链接

    老赵 2009-08-26 22:50:00

    @Jaryleely
    哪些部分看不懂?

  11. Jaryleely
    *.*.*.*
    链接

    Jaryleely 2009-08-27 08:31:00

    第二部分:类型与通用语言运行时中的基元类型,引用类型与值类型,第三部分中的事件,发布事件和侦听事件,还有就是定制特性的问题。另外垃圾收集和CLR寄宿,应用程序域。这些都不太理解。感觉书上解释的太深奥,赵老师有更通俗的解释吗?

  12. Jaryleely
    *.*.*.*
    链接

    Jaryleely 2009-08-27 08:33:00

    不好意思,可能是我理解能力太差了。麻烦赵老师能给出解答和建议。。。谢谢您!!

  13. 老赵
    admin
    链接

    老赵 2009-08-27 09:05:00

    @Jaryleely
    CLR寄宿,应用程序域可以暂时不看,其他应该不难吧。

  14. Jaryleely
    *.*.*.*
    链接

    Jaryleely 2009-08-27 09:21:00

    谢谢赵老师的建议!我再多看几遍。弄懂为止.

  15. dsjian[未注册用户]
    *.*.*.*
    链接

    dsjian[未注册用户] 2009-10-08 12:49:00

    仔细拜读了相关的一系列文章,在FastReflectionLib这里摸索了很久。试了一下,好用。谢谢

  16. capqueen
    116.231.242.*
    链接

    capqueen 2016-03-22 11:00:01

    非常棒,受益匪浅。 不过实践中发现一个问题,继承自RouteBase的DomainRoute还得实现Constraints的支持。觉得继承Route来实现感觉更加方便一些。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我