国庆前的最后一天,我写了《关于ASP.NET Routing的几点内容》,其中谈论了ASP.NET Routing作用,设计目的,工作流程等等。不过我还有一个比较重要的东西一笔带过了,不知道您注意到了没有,在向ASP.NET Routing的RouteTable.Routes属性(一个RouteCollection对象)中添加Route规则的时候,我们会同时指定一个“名称”,在微软给出的官方“广告”中,似乎看不出这个名称有什么用。但事实上,它的功能非常关键。
那么我们通过一个实际的例子来看一下,Route规则的“名称”到底有什么作用:
routes.MapRoute( "Article.List", // route name "articles/{*searchCriteria}", // pattern new { controller = "Article", action = "List" }); // defaults routes.MapRoute( "Product.List", // route name "products/{*searchCriteria}", // pattern new { controller = "Product", action = "List" }); // defaults
以上两个Route规则,分别是“文章(article)”和“产品(product)”的搜索URL。与微软“广告”中一直使用的{controller}/{action}/{id}不同,我们实际生产过程中,一些如controller或action这样的数据无法从URL中捕获到,因此我们只是“匹配URL”并且“指定默认值”。就“捕获URL”这方面,这样的配置非常完美。但是我也多次强调,Route规则还会负责“生成虚拟路径”。那么这样的配置在这方面会有什么问题呢?请看下面这个数据:
var data = new { controller = "Product", action = "List", searchCriteria = "..." }
我们可以使用这个匿名对象来构造一个RouteValueDictionary,其中包含了生成虚拟路径所需要的数据。于是我们可能会调用这个方法来得到结果:
RouteTable.Routes.GetVirtualPath(requestContext, new RouteValueDictionary(data));
很显然,这个数据是要生成“产品”的搜索路径,但是您猜一下,我们能够得到正确结果吗?
不行。
因为RouteCollection的GetVirtualPath方法会依次遍历其中所有Route对象,访问其GetVirtualPath方法,并把第一个非null参数作为结果。如果项目中的配置如以上两个,则这样的数据永远会被Article.List这个规则捕获到——于是生成的永远便是Article.List的虚拟路径了。
对于这个问题一般有两种解决方法,一是为Route规则添加“约束”:
routes.MapRoute( "Article.List", // route name "articles/{*searchCriteria}", // pattern new { controller = "Article", action = "List" }, // defaults new { controller = "Article", action = "List" }); // constraints
于是,这个Route对象在生成虚拟路径的时候,就会关注当前的RouteValueDictionary中包含的值是否符合约束。如果不符合,则直接返回null。如此,RouteCollection便会继续尝试下一个Route规则——可能就是Product.List,于是我们得到了想要的数据。
第二种解决方法便是在构造URL的时候指定一个名称:
RouteTable.Routes.GetVirtualPath(requestContext, "Product.List", new RouteValueDictionary(data));
如此,RouteCollection便会直接去找到Product.List所对应的Route对象,并且直接使用它的GetVirtualPath方法获得虚拟路径——即使返回null,也不会尝试其他Route规则。这么做的好处是避免Route规则之间在生成虚拟路径时产生冲突,并且由于避免了便利所造成的无效尝试,于是程序性能也会有些许提高。
在MvcPatch中也有对Route配置名称的支持:在MvcPatch.Extensions.dll中包含一个RouteNameAttribute对象,我们可以用它对某个Action方法进行标记,如:
[RouteName("Product.List")] public ActionResult List(string searchCriteria) { ... }
于是,如果您在View中使用这样的方法来构造URL:
<%= Url.Action<ProductController>(c => c.List(...)) %>
MvcPatch便会查看这个Action方法是否有RouteName标记。如果有,则通过直接指定名称的方式去构造虚拟路径,否则便还是传统的“遍历”方式。