简化DomainRoute的配置
2009-10-15 09:57 by 老赵, 16585 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框架有所顾虑的话,也可以参考其中的实现,有选择地运用到自己的项目中去。
我也抢个老赵的沙发,这个是个很有用的东西,看看到时候需要也拿来用用,不会写还不会拿老赵的吗,哇哈哈