ASP.NET MVC URL重写与优化 使用Global路由表定制URL
一,认识Global路由表
我们新建一个ASP.NET MVC Web程序的时候,会生成一个Global.asax文件。如下:
using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using System.Web.Routing; 7 8 namespace JohnConnor.Web 9 { 10 // 注意: 有关启用 IIS6 或 IIS7 经典模式的说明, 11 // 请访问 http://go.microsoft.com/?LinkId=9394801 12 13 public class MvcApplication : System.Web.HttpApplication 14 { 15 public static void RegisterGlobalFilters(GlobalFilterCollection filters) 16 { 17 filters.Add(new HandleErrorAttribute()); 18 } 19 20 public static void RegisterRoutes(RouteCollection routes) 21 { 22 routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 23 24 routes.MapRoute( 25 "Default", // 路由名称 26 "{controller}/{action}/{id}", // 带有参数的 URL 27 new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 28 ); 29 30 } 31 32 protected void Application_Start() 33 { 34 AreaRegistration.RegisterAllAreas(); 35 36 RegisterGlobalFilters(GlobalFilters.Filters); 37 RegisterRoutes(RouteTable.Routes); 38 } 39 } 40 }
首先Application_Start()是Web应用程序启动的时候的入口。<RegisterGlobalFilters()方法是用来注册全局筛选器的,与本篇内容关系不大>
而RegisterRoutes()方法是用来注册路由表的
这里已经有了两条默认的路由规则:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
IgnoreRoute()是RouteCollection路由表类的扩展方法,用于忽略指定的路由请求。这句意思是忽略对扩展名为.axd文件的请求。<这个方法不在此详述>
我们主要来看这一条路由规则:
routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参数默认值 );
MapRoute()方法是一个添加路由映射的方法(RouteCollection类的扩展方法)。这里是它最常用的一个重载,映射指定的 URL 路由并设置默认路由值:
- "Default"是路由的名称,这个名称在应用程序的路由的集合(routes对象)中是唯一的,如果重名生成时就会报错。
- "{controller}/{action}/{id}"代表的是路由的 URL表达式。通过{}来定义的是占位符。
- new { controller = "Home", action = "Index", id = UrlParameter.Optional } 声明了一个包含默认路由值的object匿名对象。
这条语句声明了 routename 和 url表达式 并默认 将该URL表达式映射到某个Controller下的某个Action方法中。
这是一条默认的路由规则。
发布网站的时候Global.asax文件会被编译成DLL。程序启动的时候就会首先调用Application_Start()方法,我们的路由表注册后,默认路由规则就生效了。
如果没有什么特殊要求的系统,比如网站后台,就不用再折腾了,一条默认路由规则足矣。
二,路由匹配规则
1.首先我们需要科普一下如何来定义一条URL表达式。
首先URL表达式都是相对的,不包括主机域名部份(比如http://www.xxx.com)。{}保存的是占位符,“/”,“.”则用来作为分隔符,什么都有没则是静态内容:
- URL /category/showcategory/1000 匹配 "{controller}/{action}/{id}"。
- URL /product/2012/4/28.html 匹配 “/product/{year}/{month}/{day}.html” ,诸如此类。
这里需要注意的是{controller}和{action}是保留的两个占位符,分别代表对应的控制器名称和操作名称。
{controller}对应控制器的名称,这里规定是CategoryController去掉Controller后缀的部份,即Category
{action}对应控制器内的Action方法的名称。
2.路由有两种不同的操作。
URL定向,当你在浏览器输入一个URL时,程序会在我们添加的路由表中通过对比URL表达式进行匹配,找到对应的包含路由值的对象,从而定位到具体的资源上。
我们来看一个例子,我们来添加两条路由规则。
routes.MapRoute("Test", "where-are-you-going", new { controller = "Home", action = "Index" }); routes.MapRoute("Test1", "where-are-you-going", new { controller = "Home", action = "Others" });
假设HomeController里有两个Action 分别是Index和Others。
各位看官觉得在浏览器输入http://www.xxx.com/where-are-you-going 会进入哪个方法体中呢?如果上下颠倒一下呢?
<谁写在上面就进入其指定的Action 中>
获取URL,下面这段代码就使用Url.Action方法通过Controller和Action名完全限定了一个URL,
即我们有了一个路由值,通过在路由表中匹配,可以找到对应的URL模式,进而生成一个URL
<a href="@Url.Action("Index", "Home")">主页</a>
现在我们在默认的路由规则下再添加一条如下的路由规则
routes.MapRoute("MyHome", "myhome/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
你觉得页面会显示什么呢?
<a href="http://www.xxx.com/">主页</a> or <a href="http://www.xxx.com/myhome">主页</a>
如果把这条路由规则写在默认规则的上面呢?
<谁写在上面就显示其匹配的URL>
3.上面的问题大家可以自己尝试一下,很明显,路由的匹配是 自上而下 的,只要匹配到第一条记录,就会返回对应URL或者路由值。
这一点非常重要。很多人在定制路由规则的时候,总是发现自己的规则不生效。那么你就应该检查是不是被前面的路由覆盖掉了。
三,解决开始的问题
从这个站的URL可以看出,
http://www.xxx.com/category/showcategory?categoryid=1000&view=list&orderby=price&page=1,
用的应该只是默认路由规则,
可以推断出有一个名为Category的控制器,其中有个方法名为ShowCategory,必选参数为categoryid,其他为可选参数。
根据上面介绍的Global路由表来完成URL重写的知识,
按照站长的要求,我们只需要添加一条路由规则就完事了。是不是很简单?
routes.MapRoute("Category", "category/{categoryid}", new { controller = "Category", action = "ShowCategory" }
这时候一定要注意喔,不要写在默认路由的下面,你懂得。不然你就悲剧了。
强烈建议大家自己动手玩一玩Global文件,重写URL来亲身感受一下。其实有一个悬念我留给大家了,
为什么默认规则下获取首页的URL路由是“/”而不是“/home/index”呢。^_^
匹配规则还有贪婪匹配和缺省匹配一说,这个留大家去了解把。
使用Global还是有很多的局限性的,上例中因为方法ShowCategory有一个参数categoryid,它存在与路由的键值对中,
在配置时我们就可以使用占位符来{categoryid}来显示它。
换作http://www.xxx.com/categoryname 这样的URL,如果不修改程序,Global是无能为力的,因为categoryname并不存在与路由的键值对中。
应对这样的较为复杂多变的URL重写与优化的需求(需求总是复杂多变的=。=)。
在写完第一篇和第二篇MVC路由文章后,我觉得还不够,还应该再具体一点说一下分类筛选与MVC路由,如何快速完成一个分类筛选,分类筛选的高亮如何实现,以及MVC路由参数的顺序对参数值的影响等等。
今天要说的:
一 MVC路由参数的顺序对参数值的影响
二 快速完成一个分类筛选
三 根据RequestContext.RouteData来实现高亮的菜单
一 MVC路由参数的顺序对参数值的影响
我们看这两个路由的规则,几乎是相同的,但有个地方你是否注意到,在路由规范中grade与subject参数的顺序是不同的,这个参数顺序在程序中启到什么作用呢?经过实验证明,参数靠后的值会在routedata字典中保留,即当你改变subject时,grade的值会被保留,而当你改变grade时,subject将会被进行初始化,这也是正常的。
而切换科目时,年级信息会被保留
原来就是grade在RouteData字典中的索引排在subject这
二 快速完成一个分类筛选
代码如下:
<div class="banner"> <span id="grade0">@Html.ActionLink("全部", "About", new { grade = 0 })</span>
<span id="grade1">@Html.ActionLink("一年级", "About", new { grade = 1 })</span>
<span id="grade2">@Html.ActionLink("二年级", "About", new { grade = 2 })</span><hr /> <span id="subject0">@Html.ActionLink("全部", "About", new { subject = 0 })</span> <span id="subject15">@Html.ActionLink("语文", "About", new { subject = 15 })</span> <span id="subject16">@Html.ActionLink("数学", "About", new { subject = 16 })</span> <hr /> </div>
三 根据RequestContext.RouteData来实现高亮的菜单
我们的当前URL地址上的信息被以路由的形式存储到了“当前HTTP上下方”的RouteData字典里,包括你的controller及action,还有就是查询参数集合等,我们可以通过这个东西来实现当然
菜单的高亮显示功能,看JS和CSS代码:
1 <style type="text/css"> 2 .banner { background: #eee; margin: 5px; padding: 5px; } 3 </style> 4 <script type="text/javascript"> 5 $(function () { 6 $('#grade@(Request.RequestContext.RouteData.Values["grade"])') 7 .css("background", "#000000") 8 .find("a").css("color", "#ffffff"); 9 $('#subject@(Request.RequestContext.RouteData.Values["subject"])') 10 .css("background", "#000000") 11 .find("a").css("color", "#ffffff"); 12 }); 13 </script>
怎么样,这个功能模块很实用吧,呵呵!
var defaults = new RouteValueDictionary { { "classname", "news" } }; var constaints = new RouteValueDictionary { { "classname", @"news|baike" }, }; var dataTokens = new RouteValueDictionary { { "defaultclassname", "news" }, { "Namespaces", new string[] { "BtxCMS.Web.Controllers" } } }; RouteTable.Routes.MapPageRoute("New", "{classname}/index.html", "~/Views/New/Page.cshtml", false, defaults, constaints, dataTokens); RouteTable.Routes.MapPageRoute("New_Index", "{classname}/index.html", "~/Views/New/Page.cshtml", false, defaults, constaints, dataTokens); RouteTable.Routes.MapPageRoute("New_Page", "{classname}/{Page}", "~/Views/New/Page.cshtml", false, defaults, constaints, dataTokens);