Hello World
Spiga

简化DomainRoute的配置

2009-10-15 09:57 by 老赵, 15381 visits

昨天有朋友写邮件告诉我说,他正在项目中尝试着使用我提供的DomainRoute组件。我很高兴,这说明我的努力不是在自娱自乐,是对别人有实际帮助的,也有一些朋友会尝试着自行对项目进行扩展,而不总是靠微软提供的食物来过活。不过他说,他发现DomainRoute的配置非常繁琐,需要为每个Route使用WithDomain,提供了大量重复的信息。他说他也在构建了辅助API,不过似乎效果不够好,问我有没有更好的解决方法。其实是有的,因为我在使用DomainRoute的初期也遇到了这个问题,不过现在已经在MvcPatch中提供了个人认为比较令人满意解决方案。

DomainRoute使用了装饰器模式,是在某个RouteBase对象外部再包裹一层,得到一个新的DomainRoute对象。因此它的构造函数是这样的:

public DomainRoute(RouteBase innerRoute, string pattern, RouteValueDictionary defaults, ...)
{
    ...
}

此外,还提供了一个扩展方法辅助DomainRoute的构造方式:

public static class RouteExtensions
{
    public static DomainRoute WithDomain(this RouteBase route, string pattern, ...)
    {
        return new DomainRoute(route, pattern, ...);
    }
}

于是在使用的时候:

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

这只是一条配置规则而已,的确够繁琐的。在实际项目中,这样的配置大概会出现十几次甚至更多,更重要的是,这些在同一个域名的配置规则,需要为每一个重复一遍配置的信息,这严重违反了DRY原则。最容易想到的做法,可能是将WithDomain方法的调用提取到另一个方法中去,然后反复调用同一个方法,便可以缓解这个状况了。

不过,和微软在ASP.NET MVC提供的配置Route规则的做法相比,还是有比较大的差距。我们来回忆一下,它是怎么做的呢?

routes.MapRoute(
    "Default",                                                  // name
    "{controller}/{action}/{id}",                               // url
    new { controller = "Home", action = "Index", id = "" });    // defaults

多容易,你看它根本没有“构造”Route类,指定MvcRouteHandler对象这样的操作。这就是在告诉系统“我们要做什么”,而不是“我们该怎么做”。MapRoute是一个扩展方法,会向RouteCollection对象中(也就是上面的routes)添加Route规则。于是,我们也可以为DomainRoute添加一个类似的辅助方法。那么先来看看它的使用方式吧:

routes.MapDomain(
    "Blog",                                                     // prefix
    "http://{userName}.blogs.{*domain}",                        // domain
    new { area = "Blog" },                                      // defaults
    innerRoutes =>
    {
        innerRoutes.MapRoute(
            "Index",                                            // name
            "",                                                 // url
            new { controller = "Blog", action = "Index" });     // defaults

        innerRoutes.MapRoute(
            "Default",
            "{controller}/{action}/{id}",
            new { controller = "Home", action = "Index", id = "" });
    });

routes.MapDomain(
    "API",
    "http://api.{*domain}",
    new { area = "API" },
    innerRoutes =>
    {
        ...
    });

上面的代码表示为当前项目配置了两个域名,而在第一个DomainRoute下面还配置了两条Route规则。MapDomain方法的最后一个参数是一个“Route收集器”,它的作用是收集一些Route规则。这个收集器上面也定义了MapRoute方法,它的使用方式和ASP.NET MVC中自带的做法保持统一。使用了这样的API之后,我们可以看到上面的信息没有任何的重复,也不需要定义额外的辅助方法。

值得一提的是,虽然这个API的形式从一开始我打算简化DomainRoute配置时就定下了,但是它的内部的实现却有过很大的改变。一开始,我是打算把DomainRoute使用组合模式做成一个容器,其中包含了N个Route配置。但是昨天的文章提到,由于Route规则是有“命名”的,因此不能使用这种方式。因此,目前的API并不是这样实现的。

还是以上面的代码为例,我们在MapDomain方法中指定了一个名称“Blog”,它的收集器收集了两个Route规则,分别叫做“Index”和“Default”。因此,最终RouteCollection上同样会放入两个Route对象,分别叫做“Blog.Index”和“Blog.Default”——这就是MapDomain的第一个参数是“前缀(prefix)”而不是一个“名称(name)”。于是,在生成URL的时候,我们就可以通过指定名称的方式来访问到两个Route规则了。此外,如果您将prefix指定为空字符串或null,它便会保留收集器中的Route命名(如上面的Index和Default)。

关于MapDomain方法的实现,您可以参考MvcPatch中MvcPatch.Extensions项目的RouteCollectionExtensions类。MvcPatch包含我在博客上提过的几乎所有扩展,并且根据平时实际使用情况进行了改进。如果您对直接使用一个“修改版”的ASP.NET MVC框架有所顾虑的话,也可以参考其中的实现,有选择地运用到自己的项目中去。

Creative Commons License

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

Add your comment

11 条回复

  1. ︶ㄣ木べ头
    *.*.*.*
    链接

    ︶ㄣ木べ头 2009-10-15 10:17:00

    我也抢个老赵的沙发,这个是个很有用的东西,看看到时候需要也拿来用用,不会写还不会拿老赵的吗,哇哈哈

  2. Tarence.L.Xiong
    *.*.*.*
    链接

    Tarence.L.Xiong 2009-10-15 10:29:00

    看老赵的东西总是让人受益匪浅啊。。

  3. McJeremy&Fan
    *.*.*.*
    链接

    McJeremy&Fan 2009-10-15 10:54:00

    通过代码配置的方式感觉怎么写都繁琐。
    如果把这些配置提到xml中去呢?

  4. 老赵
    admin
    链接

    老赵 2009-10-15 11:20:00

    @McJeremy&Fan
    不是吧,我觉得xml更繁琐,现在的趋势就是脱离xml配置这种复杂的东西。

  5. 老农
    *.*.*.*
    链接

    老农 2009-10-15 11:23:00

    老赵不组织个开源产品,或通用框架什么的,真是...了

  6. 老赵
    admin
    链接

    老赵 2009-10-15 12:37:00

    @老农
    没有好的方向啊。

  7. RicoRui
    *.*.*.*
    链接

    RicoRui 2009-10-15 14:17:00

    #4楼[楼主] Jeffrey Zhao
    2009-10-15 11:20
    @McJeremy&Fan
    不是吧,我觉得xml更繁琐,现在的趋势就是脱离xml配置这种复杂的东西。
    ---
    那脱离xml配置之后,有什么新的解决方案吗?你指的现在趋势是什么?Jeffrey Zhao能分享一下吗?

  8. 老赵
    admin
    链接

    老赵 2009-10-15 14:21:00

    @RicoRui
    一个是约定胜于配置,还有就是代码即配置吧。
    因为配置往往本来就是和代码绑定的,那么为什么不用代码来表示配置呢?
    例如这里的routing规则,提取成外部xml配置,难道不总是和代码一起改的吗?

  9. Claus2046[未注册用户]
    *.*.*.*
    链接

    Claus2046[未注册用户] 2009-10-15 15:00:00

    但对于一些定义好的逻辑,xml配置文件还是很有用吧,类似ant的配置文件,增添些配置就很灵活不需要重新编译代码。
    动态语言做代码即配置到是很舒服,像django本身的配置文件就是一个py文件。
    不过也强烈赞同xml配置很繁琐,可以考虑配置文件用些轻量级的yaml之类,写起来就不会像xml感觉n多冗余。

  10. RicoRui
    *.*.*.*
    链接

    RicoRui 2009-10-15 16:55:00

    约定胜于配置这个知道,Rails的设计不就是这个吗。
    不过代码即配置怎么让我感觉又搞回去了。
    (以前开发时不推荐把配置写在代码中,而是一种开放的形式进行展现)
    以前为什么采用配置的原因是因为,如果当前角色不是开发人员,或者是不懂开发的人员,这样的好处是对程序可以进行最少量DIY的配置。
    如:
    去掉工作流的第三个check环节,不对第二个环节的产生结果进行检查操作;
    等等场景
    再看代码即配置,如果将配置即代码的操作,配置的工作不是只能是开发的人员完成了。
    还是说代码即配置,我的理解有点问题?

  11. 老赵
    admin
    链接

    老赵 2009-10-15 17:16:00

    @RicoRui
    当然,代码即配置不可能适用于任何情况,你说在外部调整工作流,可能就不适合了。
    把配置写在代码里,适用于那种如果要改配置,基本上就需要改代码的情况。
    比如,在ORM中,O变了,M也肯定要变。那么M为什么要用外部XML,而不是和O一样写在代码里呢?
    基本上找不到O不变,但M却要变化的情况,不是吗?

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我