文章目录:
1、认识理解URL,以及简单的路由
2、特性路由、传统路由、区域路由
3、路由生成URL&&绑定到操作&&路由约束
1、认识理解URL,以及简单的路由
默认简单的路由(概述、路由&URL重写)
先来看下两个名词 URL(统一资源定位符),URI(统一资源标识符)
高质量URL应该满足的规则:域名便于记忆和拼写、域名简短、便于输入、反映出站点的结构、“可破解的”,用户可以通过移除URL的末尾,进而到达更高层次的信息体系结构,持久不能改变
路由概述:
asp.net mvc 的路由主要有两种用途 1、匹配传入的请求,并把这些请求映射到控制器操作。2、构造传出的URL,用来响应控制器的操作
路由&URL重写
它们的共同点是都可以为搜索引擎优化,构建漂亮的URL,不同的是URL重写试讲一个URL映射到另外一个URL,路由关注的是将URL映射到资源;路由使用它的映射规则匹配传入的URL帮助生成URL,URL重写只能用于传入的URL,不能帮助生成原始的URL。
2、特性路由、传统路由、区域路由
特性路由:
让我们来看下什么是特性路由,首先创建一个MVC项目,来到网站的入口点Global.asax文件,会看到在Application_Start 方法里面,存在一个方法 RouteConfig.RegisterRoutes(RouteTable.Routes);这个方法是集中控制路由的地方,这个方法对应着RouteConfig.cs文件的RegisterRoutes方法,因为我们要测试特性路由,因此我们把代码这样:
public static void RegisterRoutes(RouteCollection routes) { //routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); //routes.MapRoute( // name: "Default", // url: "{controller}/{action}/{id}", // defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } //); routes.MapMvcAttributeRoutes(); }
路由的核心工作是将一个请求映射到一个操作。静态路由,接下来,我们在Home控制器下的About方法上面使用一个特性
[Route("about")] public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); }
那当我们URL为about的时候、特性路由就会运行Home控制器的about方法,如下图所示:
例如我们的首页,想要由多个匹配规则全都能匹配到,这时候就可以如此定义特性路由
[Route("")] [Route("index")] [Route("home/index")] public ActionResult Index() { return View(); }
路由值
例如我们现在要根据一个人的Id查询这个人的一些信息,此时Id作为参数传入,我们可以这样定义路由
[Route("person/{id}")] public ActionResult Details(int id) { //Query person return View();
我们的URL为person/1时候,就会访问到Details方法,参数Id值为1。也就是说当特性路由匹配并运行操作方法时,模型绑定会使用路由的路由参数为同名的方法参数填充值
控制器路由
我们可以使用特殊的路由参数"action"来匹配某个控制器下所有的方法
[Route("home/{action}")] public class HomeController : Controller { //other action }
我们可以使用RoutePrefix 特性仅在一个地方制定路由开头
[RoutePrefix("home")] [Route("{action}")] public class HomeController : Controller { [Route("")] [Route("index")] public ActionResult Index() { return View(); } public ActionResult About() { ViewBag.Message = "Your application description page."; return View(); } public ActionResult Contact() { ViewBag.Message = "Your contact page."; return View(); } }
此时访问URL /index 和 /home/index就会如下结果
加入我们想直接访问Home控制器的Index方法,我们就可以在Index方法上面添加以下特性路由,这样我们的URL 为/,/home,/home/index就都可以访问到Index方法了
[RoutePrefix("home")] [Route("{action}")] public class HomeController : Controller { [Route("~/")] [Route("")] [Route("index")] public ActionResult Index() { return View(); }
路由约束:
例如我们现在控制器里面有两个方法来查找一个人=》
[Route("person/{id}")] public ActionResult Details(int id) { //Query person return View(); } [Route("person/{name}")] public ActionResult Details(string name) { //Query person return View(); }
此时会发现,无论我们的URL 为/person/1 还是/person/bob 都能被[Route("person/{name}")]匹配到,这是我们的特性路由就存在二义性,这时候我们需要给路由添加一个约束,如下图所示,这样/person/1就会被匹配到[Route("person/{id:int}")] 这个路由,这种约束叫做“内联约束”。
[Route("person/{id:int}")] public ActionResult Details(int id) { //Query person return View(); } [Route("person/{name}")] public ActionResult Details(string name) { //Query person return View(); }
传统路由
接下来我们来看一下传统路由,首先,修改RouteConfig 文件中的RegisterRoutes方法,如下所示
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}"); }
MapRoute方法的第二个参数我们称他为路由模板。路由模板和特性路由相同,是一种匹配规则,目的是将URL链接到操作方法,它们两者的不同点是完成这个操作传统路由依赖于名称字符串,特性路由依赖于特性。
例如现在, 我们的请求URL 为/home/index,根据mvc的约定,mvc 会把Controller 添加到{controller}的参数后面,然后请求{action} 方法。
路由值
我们的路由除了必须的{controller} 和 {action} 参数以外,还可以存在第三个参数,例如下面的代码,我们的RouteConfig方法,我们的路由模板存在{id}这样一个参数,我们称它为路由值,当我们请求URL :/Person/Details/1的时候,该请求会导致MVC实例化PersonController类,调用Details方法,并将1传递给Details方法中的参数Id。
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}/{id}"); }
例如我们想让所有请求都用site/开头,我们就可以这样定义路由模板。
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "site/{controller}/{action}/{id}"); }
路由模板定义非常灵活,但是允许连续定义两个路由参数,例如这样就是错误的:{controller}{action}/{id};
路由默认值:
当我们的路由规则是 {controller}/{action}/{id} 这样的时候,我们要相匹配Person/Index是匹配不到的(因为不存在Id参数),这是我们需要重新定义我们的路由,给MapRoute方法添加第三个参数
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}/{id}" ,new { id=UrlParameter.Optional}); }
new { id=UrlParameter.Optional}这段代码定义了参数{id}的默认值,参数{id}为可选参数,当然我们也可以定义默认的控制器和方法,如下:
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}/{id}" ,new {controller="Home", action = "index", id=UrlParameter.Optional}); }
路由约束:
当我们要请求这样一个控制器方法的时候,就可以使用路由约束。由于路由是从上往下匹配,当我们第一个路由规则匹配成功后,就不再往下匹配,所以,当我们请求URL为/2012/01/01时候,就会被第一个规则匹配到,进入到Home 控制器的Contact方法,当我们请求URL类似/Home/Index/123的时候,就会被第二个路由规则匹配到。
public ActionResult Contact(int year,int month,int day) { ViewBag.Message = "Your contact page."; return View(); }
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("home", "{year}/{month}/{day}" ,new {controller="home", action = "contact", id=UrlParameter.Optional} ,new { year =@"\d{4}", month = @"\d{2}", day= @"\d{2}" }); routes.MapRoute("simple", "{controller}/{action}/{id}"); }
当然我们可以一起使用特性路由和传统路由,如下所示
public static void RegisterRoutes(RouteCollection routes) { routes.MapMvcAttributeRoutes(); //把特性路由放在传统路由的上面,URL优先匹配特性路由 routes.MapRoute("home", "{year}/{month}/{day}" ,new {controller="home", action = "contact", id=UrlParameter.Optional} ,new { year =@"\d{4}", month = @"\d{2}", day= @"\d{2}" }); routes.MapRoute("simple", "{controller}/{action}/{id}"); }
在RouteConfig 的RegisterRoutes方法中,是可以给路由命名的,如下所示,我们使用mvc为我们提供的razor html标签(就是htmlHelper对象。。)的时候可以指定使用那个路由规则进行匹配,如下所示:
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}/{id}" , new { controller = "Home", action = "index", id = UrlParameter.Optional }); routes.MapRoute("home", "{controller}/{action}/{id}" , new { controller = "Home", action = "about", id = UrlParameter.Optional }); }
@{ ViewBag.Title = "Index"; } <h2>Index</h2> @Html.RouteLink( linkText: "route home", routeName: "home", routeValues: new { controller = "Home", action = "index", id = 123 }) @Html.RouteLink( linkText: "route simple", routeName: "simple", routeValues: new { controller = "Home", action = "about", id = 123 })
区域路由
mvc2.0 引入区域概念,可以将我们的网站划分为若干个节点,如下所示,我们添加了一个区域,在这个区域中,也存在一个叫做Home的控制器和Index的方法。然后我们访问这个网站,就会这样。。
这个时候,我们需要给项目的路由指定一组用来定位控制器类的名称空间
public static void RegisterRoutes(RouteCollection routes) { routes.MapRoute("simple", "{controller}/{action}/{id}" , new { controller = "Home", action = "index" ,id =UrlParameter.Optional} ,new[] { "MVCSummary.Controllers" }); }
catch-all 参数,我们如下定义路由模板,{*extrastuff}被称为catch-all 参数参数,catch-all 参数可以匹配任意段的URL,但是只能作为路由模板的最后一段
public static void RegisterRoutes(RouteCollection routes) { routes.MapMvcAttributeRoutes(); routes.MapRoute("simple", "{controller}/{action}/{id}/{*extrastuff}" , new { controller = "Home", action = "index" ,id =UrlParameter.Optional} ,new[] { "MVCSummary.Controllers" }); }
3、路由生成URL&&绑定到操作&&路由约束
路由除了能匹配传入的请求URL意外,还有一个重要的职责就是生成URL。
路由的核心是RouteCollection基类和RouteBase基类。
RouteCollection类有两个重载方法GetVirtualPath,第一个方法传入请求上下文,和用户指定的路由值(一个字典),路由集合通过这个方法遍历每个路由匹配路由模板,假如匹配成功,则返回一个VirtualPathData对象,该对象包含生成的URL实例和用户定义的路由集合,见下图:
第二个GetVirtualPath方法要求传入一个Routename, 它不会挨个遍历路由集合、假如找不到这个名称的路由或者匹配失败,就会返回空值,不再进行匹配。找到这个路由匹配成功则会返回一个VirtualPathData对象。
整个URL的生成如下图所示:
解释个名词,溢出参数:URL生成过程中使用但是没有在路由定义中指定的路由值。 例如我们的请求URL为/Home/Index/1?Page=1 ,这里的Page=1就是溢出参数
接下来让我们看几个Route类生成URL的例子
routes.MapRoute("report", //这是我们的路由模板 "{year}/{month}/{day}", new { controller = "Home", action = "Index", day = 10 });
//调用Url.RouteUrl
@Url.RouteUrl(new { param1 = value1, paraml2 = value2,..., paramlN = valueN });
参数 | 返回URL | 说明 |
year=2017,month=12,day=08 | /2017/12/08 | 直接匹配模板 |
year=2017,month=12 | /2017/12 | day默认为10 |
year=2017,month=12,day=08,Page=10 | /2017/12/08?Page=10 | Page溢出参数 |
year=2007 | null | 参数不足,匹配失败 |
路由绑定操作&自定义约束
路由是如何绑定到操作的呢,如下图所示:
在ASP.NET MVC中,MvcHandler实现了IHttpHandler接口,用来实例化控制器,并调用控制器上面的方法。
路由数据:RouteCollection的GetRouteData方法会返回一个RouteData实例,这个实例里面包含了匹配请求的路由信息。当外界URL匹配成功时,它就创建一个字典用来保存路由参数,这个字典为Values,例如当路由模板为{controller}/{action}/{id} ,请求URL 为/Home/Index/123 则Values字典中应至少包含三个键controller、action、id,它们的值分别为Home、Index、123;特性路由的字典保存在DataTokens这个字典用。
自定义约束:
路由提供了一个接口IRouteConstraint ,这个接口有一个Math方法,当路由匹配完模板之后,假如有约束实现了IRouteConstraint接口,就会导致路由引擎调用Math方法,来确定是否满足给定请求。
例如我们想要一个只实现Get请求的路由,HttpMethodConstraint继承自IRouteConstraint接口。
routes.MapRoute("getmap", "{controller}", null, new { httpmethod = new HttpMethodConstraint("Get") });