asp.net MVC 的URL路由是一个非常强大的功能,而且有个优点:强大单不复杂。然而,目前我在网上看到的相关资料,却都仅仅提供一些示例,仅通过这些示例,初学者基本上不可能明白为什么要这么配置,更不用说灵活运用了。
这篇文章将采取循序渐进的方式,来解释MVC URL路由的配置方法,使初学者能够完全掌握MVC URL路由的配置方法。
阅读本文的用户必须有如下基础:
1、熟悉C#;
2、能够通过Visual Studio建立asp.net MVC项目;
3、能够在MVC项目中创建Model、Controller、Action和View。
4、对url的结构有明确的认识。
基于上述几点,本文的读者应该是熟悉Asp.net web开发的MVC初学者。如果阅读中发现有理解困难的地方,请自行补充上述知识。
本文使用Asp.net MVC4作为示例。
一、默认路由
项目的默认路由如下:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
这条路由实际上是使用命名参数的方式调用routes这个对象的MapRoute方法。上述的代码也可以这么写:
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });
这个方法有三个参数:name、url和defaults。
name这个参数表示本条路由信息的名称,必须为唯一,如果有重复,则网站启动时会出现以下错误:
图片1:路由名称重复
URL表示本条路由信息的URL模板。这里引入一个术语:URL模板。
"{controller}/{action}/{id}"这个URL模板表示url由三部分组成:controller、action和id,每一部分之间用斜杠(/)分割。对于一个形如:
http://www.xxx.com/aaa/bbb/cc 的url,aaa就表示controller,bbb就表示action,cc就表示id。如果有一个名为aaa的Controller,这个Controller中有一个名为bbb的Action,这个Action接收一个名为id的参数,那么当我们访问:
http://www.xxx.com/aaa/bbb/cc 这个url时,MVC就会调用aaaController中的bbb方法,并把cc作为参数id的值传递进去。
defaults表示如果模板中的某个项目缺失,则用指定的值来设置这个项的值。
例如:如果我们访问的URL如下:
http://www.xxx.com/aaa/bbb
也就是没有参数id,那么bbb这个方法的参数id就会用defaults中的值代替,这里一般为null。如果bbb的这个参数是值类型,那么就会报错:
图片:参数不能为null
为了防止这种情况,有以下两种处理方法:
1、路由中为值类型的参数设置默认值,而不是使用UrlParameter.Optional;
2、在action的定义中避免使用值类型的数据,如果必须使用,则使用此值类型的Nullable泛型。
如果我们访问的URL如下:
http://www.xxx.com/aaa
也就是同时没有了action和id,那么这两个项目都会用defaults中的值代替,也就是会访问aaaController中的Index方法,并传递一个null的id。
同理,如果仅仅输入域名,那么就会访问HomeController中的Index方法,并传递一个null的id。
下面对默认路由进行一点点改动,改动后如下:
routes.MapRoute(
name: "Default",
url: "{action}/{controller}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
问题一:请写出访问如下URL时,访问了哪个controller的哪个action,传递了什么参数。
1、http://www.xxx.com/aaa/bbb/cc
2、http://www.xxx.com/aaa/bbb
3、http://www.xxx.com/aaa
4、http://www.xxx.com
答案在本文最后。
简单总结一下:
当用户输入的URL和某条路由规则中的url模式匹配时,根据这个url模式在输入的URL中查找url模式中的项目,如果查找不到,则用默认值代替。
二、参数传递
1、基本类型的参数传递。
如果在aaaController中有一个叫做ddd的action,方法定义如下:
public ActionResult ddd(string dParam)
这个action并不接收参数id,而是接收一个叫做dParam的string型参数。如果要访问这个action,则url应该如下:
http://www.xxx.com/aaa/ddd?dParam=xxx
这样,ddd这个action中的dParam参数的值就会被设置为字符串xxx。
如果我们访问的url如下
如果我们访问的url如下:
http://www.xxx.com/aaa/ddd?dParam=xxx&cParam=yyy
这里url中多了一个名为cParam的值,但是ddd这个action的定义中并不包含这个参数,那么路由系统就会把cParam这个参数忽略。
再看一个url:
http://www.xxx.com/aaa/ddd
这个url后面没有带任何参数,那么ddd这个action中所需的参数dParam就会被设置为null。
2、对象类的参数传递。
再看一种情况。如果在aaaController中有一个叫做ObjectParam的action,方法定义如下:
public ActionResult ObjectParam(ObjectModel objModel)
ObjectModel的定义如下
public class ObjectModel
{
int ObjParamInt { get; set; }
string ObjParamString { get; set; }
}
如果我们访问如下url:
http://www.xxx.com/aaa/ObjectParam?ObjParamInt=10&ObjParamString=xxx
那么,在ObjectParam这个方法中,参数objModel中对应属性的值就会被设定为url中对应的值。但是如果url中的值类型和对象中的对应字段的类型不一致,则会忽略url中的值。
例如,如果我们访问如下url:
http://www.xxx.com/aaa/ObjectParam?ObjParamInt=def&ObjParamString=xxx
那么参数objModel中ObjParamInt这个属性就不会被赋值,它的值就是int型数据的默认值:0。
另外,还要注意一点,url参数中的各个项目的大小写必须和对象中的一致。
请回答下面问题:
问题二:在默认路由情况下,请写出访问如下URL时,参数的状态。
http://www.xxx.com/aaa/ObjectParam
答案在本文最后。
三、特殊路由
我们把默认路由修改为如下所示:
routes.MapRoute(
name: "Default",
url: "{controller}-{action}-{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
也就是把默认路由中的斜杠“/”换成了减号“-”。先用如下URL测试:
http://www.xxx.com/aaa-bbb-ccc
这时就会访问aaaController中的bbb这个action,同时把ccc作为参数id的值传递进去。
还记的第一部分的总结吗:当用户输入的URL和某条路由规则中的url模式匹配时,根据这个url模式在输入的URL中查找url模式中的项目,如果查找不到,则用默认值代替。
我们试验一下修改后的路由如果在url中省略某个参数会是什么情况:
http://www.xxx.com/aaa-bbb
访问后出现如下错误:
图片3:找不到文件
同样,如果用以下URL,也找不到aaaController中的Index方法:
http://www.xxx.com/aaa
也会出现上述错误。
出现这个错误的原因是,在默认路由中,各项目之间的分割符是斜杠“/”,而修改后的路由中,分割符是减号“-”。但是在MVC的路由系统中,只认为斜杠是分隔符,其他任何字符都仅仅作为模式字符串。当我们用这两个URL
访问的时候,路由系统认为找不到合适的路由模式,所以就把这个URL作为一个静态请求传递出去,当然这个路径是不存在的。这里“合适的路由模式”应该是三个字符串用减号连接,而且三个字符串中的任意一个都不能为空。
所以如果我们设计的路由没有用斜杠作为分隔符,那么URL模式中项目就无法使用defaults中的默认值。
四、进阶版
先把我总结出来的URL模板规则写出来:
1、花括号对“{}”之间的字符串为路由项目;
2、斜杠“/”为分隔符;
3、其它任何字符都是固定字符,必须在固定的位置出现。
先用这三条来验证默认的路由:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
可以看到,默认路由中有三个被花括号包裹起来的项目,他们是:controller、action和id
这三项之间用斜杠分割。
再验证一下修改后的路由:
routes.MapRoute(
name: "Default",
url: "{controller}-{action}-{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
这条路由中有三个被花括号包裹起来的项目,他们是:controller、action和id,他们之间各有一个固定字符减号“-”。
那么既然花括号中的字符串都为路由项目,那么,controller和action这两个项目和其他项目相比就没有任何特殊之处,也就意味着,这两个项目也不是必须的。那么我们可以这么定义一条路由规则:
routes.MapRoute(
name: "AboutUs",
url: "AboutUs.html",
defaults: new { controller = "Home", action = "AboutUs" }
);
这条路由意味着如果用户访问AboutUs.html这个文件,将执行HomeController中的AboutUs这个action。
看到了吗?这就是利用url路由实现的伪静态。
问题三:假设我们NewsCenterController中有一个NewsDetail的action,这个action定定义如下:
public ActionResult NewsDetail(long newsID)
请试着定义一条路由规则,实现用如下url访问NewsDetail这个action:
http://www.xxx.com/NewsContent_100.html
其中数字100是newsID。
答案在本文最后。
五、路由顺序
在MVC的路由系统中,可以定义多条路由规则,多条路由规则的name属性,如下:
routes.MapRoute(
name: "AboutUs",
url: "AboutUs.html",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
当MVC的路由系统接收到一个url时,路由系统就会按照书写的顺序,对每一条路由规则中的路由模式进行匹配,只要发现某一条路由规则中的模式符合当前url时,就使用这条路由规则来处理当前url,后面的路由规则在此次处
理中就会忽略。
假设我们的路由规则中有如下两条:
routes.MapRoute(
name: "Default1",
url: "{action}/{controller}/{id}",
defaults: new { controller = "Home", action = "AboutUs" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
那么,后面一条路由无论何时都不会被使用。
问题答案:
问题一:
这个问题的路由配置如下:
routes.MapRoute(
name: "Default",
url: "{action}/{controller}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
由于这个问题的路由配置中,路由模式为:{action}/{controller}/{id},所以第一个项目是action,第二个项目为controller,第三个项目为id,因此答案如下:
1、http://www.xxx.com/aaa/bbb/cc 访问了bbbController的aaa这个action,传递给参数id的值为cc
2、http://www.xxx.com/aaa/bbb 访问了bbbController的aaa这个action,传递给参数id的值为null
3、http://www.xxx.com/aaa 访问了bbbController的Index这个action,传递给参数id的值为null
4、http://www.xxx.com 访问了HomeController的Index这个action,传递给参数id的值为null
问题二:
这个问题使用的url如下:
http://www.xxx.com/aaa/ObjectParam
这个url访问了aaaController的ObjectParam这个action,但是没有传递任何参数,所以ObjectParam参数中ObjectModel这个对象的值均为默认值,即:
ObjParamInt = 0
ObjParamString = null
问题三:
路由应该如下配置:
routes.MapRoute(
name: "NewsContents",
url: "NewsContent_{newsID}.html",
defaults: new { controller = "NewsCenter", action = "NewsDetail", newsID = 0 }