uncle_danny

摘要:

在MVC框架之前,ASP.NET假定在请求的URLs和服务器硬盘文件之间有直接的关系。服务器的职责是接收浏览器请求,从相应的文件发送输出。

这种方法只能工作于Web表单,每一个ASPX页面既是一个文件,也是一个对应请求的自包含响应。而这对于MVC应用程序来说就无效了,因为请求是由控制器类里的行为方法处理的,而且没有磁盘上一对一关系的文件。

ASP.NET平台使用路由系统处理MVC URLs。在这篇文章中,我将向你展示怎样为你的工程使用路由系统来创建和处理强大灵活的URL。你将看到,路由系统让你创建你需要的任何形式的URLs,并且用清楚和简明的方式进行表达。路由系统有两个功能:

检查进来的URL,计算出它的意向是哪个控制器和行为方法。

生成外部URLs。这些URLs显示在视图的HTML,以至当用户点击超链接时,触发一个具体的行为方法(这时候,它又变成了进来的URL)。

在这章,我将介绍路由的定义,以及使用他们处理请求的URL,以至于用户可以调用你的控制器和行为方法。在MVC框架里,有两种方式创建路由。基于传统的路由和特性路由。如果你用过MVC框架早期的版本,你可能对传统路由比较熟悉。特性路由是MVC5新加的功能。这篇文章将分别介绍它们。

下一篇文章我将介绍如何使用那些相同的路由生成你需要包含在你的视图里的对外的URLs。以及如何使用一个叫areas的功能实现订制路由系统。

准备实例工程

HomeController:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Mvc;
 6 
 7 namespace UrlsAndRoutes.Controllers
 8 {
 9     public class HomeController : Controller
10     {
11         public ActionResult Index()
12         {
13             ViewBag.Controller = "Home";
14             ViewBag.Action = "Index";
15             return View("ActionName");
16         }
17     }
18 }

CustomerController:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Mvc;
 6 
 7 namespace UrlsAndRoutes.Controllers
 8 {
 9     public class CustomerController : Controller
10     {
11         public ActionResult Index()
12         {
13             ViewBag.Controller = "Customer";
14             ViewBag.Action = "Index";
15             return View("ActionName");
16         }
17 
18         public ActionResult List()
19         {
20             ViewBag.Controller = "Customer";
21             ViewBag.Action = "List";
22             return View("ActionName");
23         }
24     }
25 }

AdminController:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Mvc;
 6 
 7 namespace UrlsAndRoutes.Controllers
 8 {
 9     public class AdminController : Controller
10     {
11         public ActionResult Index()
12         {
13             ViewBag.Controller = "Admin";
14             ViewBag.Action = "Index";
15             return View("ActionName");
16         }
17     }
18 }

在Views的子文件夹Shared内,创建视图文件ActionName.cshtml。

 1 @{
 2     Layout = null;
 3 }
 4 <!DOCTYPE html>
 5 <html>
 6 <head>
 7     <meta name="viewport" content="width=device-width" />
 8     <title>ActionName</title>
 9 </head>
10 <body>
11     <div>The controller is: @ViewBag.Controller</div>
12     <div>The action is: @ViewBag.Action</div>
13 </body>
14 </html>

在工程属性里,选择Web选项。选择Specific Page。

运行程序,得到运行结果:

介绍URL模式

路由系统使用一个路由集合展示他的魔力。这些路由共同地组成了URL模式,或者一个系统的模式(你的应用程序能够认识和响应的URLs的集合)。

我不需要手工地书写在我的应用程序里将要支持的所有的URLs。代替地,每一个路由包含一个URL模式,它跟进入的URLs进行比较。如果URL符合这个模式,它将被路由系统处理这个URL。先来一个示例应用程序的一个URL:

http://mysite.com/Admin/Index

 

URLs能够分割成一些节。它们是URL的部分,除了hostname和查询参数query string以外,它们都是以/字符隔开的。在这个例子中,有两个节:

http://mysite.com/Admin/Index

                            first     second

第一个节包含单词Admin,第二个节包含单词Index。在人眼中,第一个节明细关联于控制器,第二个节关联行为方法。但是,当然地,我需要用一种方法表达能让路由系统理解的这种关系。下面是这样的一个URL模式:

{controller}/{action}

当处理一个进来的请求时,路由系统的工作是将请求的URL匹配到一个模式,并从URL中抽出节中定义在模式种的变量。这些节的变量使用括号{}字符来表达。这个例子有两个节变量,名字分别是controller和action。所以controller节变量的值是Admin,action节变量的值是Index。

我说匹配一个模式,因为MVC系统经常有许多路由,路由系统将比较进入的URL和每一个路由的URL模式,直到找到一个匹配的。

默认地,一个URL模式将匹配相同数量节任何的URL。例如,模式{controller}/{action}将匹配任何有两个节的URL。

请求的URL 节变量
http://mysite.com/Admin/Index controller = Admin action = Index
http://mysite.com/Index/Admin controller = Index action = Admin
http://mysite.com/Apples/Oranges controller = Apples action = Oranges
http://mysite.com/Admin No match—too few segments
http://mysite.com/Admin/Index/Soccer No match—too many segments


URL模式的两个关键行为:

URL模式是保守的,它只匹配相同节数量的模式URL。表中的第四和第五个例子。
URL模式是开放的。如果一个URL确实含有相同数量的节,模式将抽出节变量的值,而不管它可能是什么。

这些事默认行为,也是理解URL模式怎样工作的关键。后面将介绍怎样改变这个默认行为。

就像已经提到的,路由系统不知道关于MVC系统的任何信息。当从URL抽出的变量,不存在对应的controller或action的时候,URL模式仍会进行匹配。表中的第二个例子就是。我转置了在URL中的Admin和Index节,从URL抽取的值也跟着转置了,即使例子工程中没有Index这个控制器。

创建和注册一个简单的路由

一旦你理解了URL模式,你就能够使用它来定义路由。路由定义在App_Start工程文件夹里的RoutConfig.cs代码文件里。你将看到Visual Studio定义的这个文件的初始内容:

 1 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 UrlsAndRoutes
 9 {
10     public class RouteConfig
11     {
12         public static void RegisterRoutes(RouteCollection routes)
13         {
14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
15 
16             routes.MapRoute(
17                 name: "Default",
18                 url: "{controller}/{action}/{id}",
19                 defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
20             );
21         }
22     }
23 }

定义在RoutConfig.cs文件里的静态RegisterRouters方法,在Global.asax.cs文件中调用。Global.asax.cs文件在应用程序启动的时候设置一些MVC核心功能。你将看到Global.asax.cs文件的默认内容。我加粗了Application_Start方法内对RouteConfig.RegisterRoutes方法的调用。

 1 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 UrlsAndRoutes
 9 {
10     public class MvcApplication : System.Web.HttpApplication
11     {
12         protected void Application_Start()
13         {
14             AreaRegistration.RegisterAllAreas();
15             RouteConfig.RegisterRoutes(RouteTable.Routes);
16         }
17     }
18 }

Application_Start方法在MVC应用程序首次启动的时候被后台的ASP.NET平台调用。使得RouteConfig.RegisterRoutes方法被调用。方法的参数是静态属性RouteTable.Routes的值,它是一个RouteCollection类型的实例。

下面代码创建自己的URL模式。

 1 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 UrlsAndRoutes
 9 {
10     public class RouteConfig
11     {
12         public static void RegisterRoutes(RouteCollection routes)
13         {
14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
15 
16             Route myRoute = new Route("{controller}/{action}", new MvcRouteHandler());
17             routes.Add("MyRoute", myRoute);18         }
19     }
20 }

我使用构造函数字符串参数和一个MvcRouteHandler实例参数创建一个新的路由。不同的ASP.NET技术提供不同的类来应对路由行为。MvcRouteHandler是ASP.NET MVC应用程序默认使用的。创建路由后,我使用Add方法将他添加到RouteCollection对象里。

一个更方便的注册路由的方式是使用定义在RouteCollection里的MapRoute方法。下面的代码跟上面的作用一样。

 1 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 UrlsAndRoutes
 9 {
10     public class RouteConfig
11     {
12         public static void RegisterRoutes(RouteCollection routes)
13         {
14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
15 
16             routes.MapRoute("MyRoute", "{controller}/{action}");
17         }
18     }
19 }

这个方法更紧凑,主要是因为我不需要创建MvcRouteHandler类的实例了(它是在后台默认创建的)。MapRoute方法只适用于MVC应用程序。ASP.NET Web表单程序使用RouteCollection类中定义的MapPageRoute方法。

使用这个简单路由

启动这个示例应用程序,你可以看到我修改路由后的效果。如果你导航到应用程序的根路径下,你将看到一个显示错误的页面。但是如果你导航到符合{controller}/{action}模式的路由,你将看到下面的结果,说明导航到/Admin/Index的效果。

定义默认值

当我为应用程序请求默认的URL得到一个错误页面的原因,是它不匹配我定义的路由。默认的URL表达式在路由系统里是~/,这个字符串里没有匹配控制器和行为方法的节。

我解释过URL模式是保守的,它们将匹配数量相等节的URLs。我还说了这是默认行为,改变这种行为的方法是使用默认值。当URL不包含匹配值的节时,默认值就被使用。下面代码提供了默认值的例子:

 1 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 UrlsAndRoutes
 9 {
10     public class RouteConfig
11     {
12         public static void RegisterRoutes(RouteCollection routes)
13         {
14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
15 
16             routes.MapRoute("MyRoute", "{controller}/{action}", new { action = "Index" });
17         }
18     }
19 }

默认值以匿名类的属性提供。上面的例子里,我给action变量提供了默认值Index。这个路由将匹配之前的所有的两个节的URLs。例如,URL:http://mydomain.com/Home/Index被请求时,路由将抽取Home作为controller变量值,Index作为action变量值。

既然我给action节提供了默认值,路由系统也将匹配单个节的URLs。当处理一个单个节的URL时,路由系统将从单个的URL节中抽取controller值,另外使用默认值作为action变量的值。在这种方式下,我能够请求URL:http://mydomain.com/Home,触发Home控制器里的Index行为方法。

我也能够走得更远,定义一个不包含任何节变量的URLs,只依赖默认值来识别action和controller。下面的代码展示了怎样提供两个节的默认值,来映射到应用程序的根URL。

 1 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 UrlsAndRoutes
 9 {
10     public class RouteConfig
11     {
12         public static void RegisterRoutes(RouteCollection routes)
13         {
14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
15 
16             routes.MapRoute("MyRoute", "{controller}/{action}", new { controller = "Home", action = "Index" });
17         }
18     }
19 }

通过给controller和action变量提供默认,我创建的路由将匹配下面的URLs。

Number of Segments Example Maps To
0 mydomain.com controller = Home action = Index
1 mydomain.com/Customer  controller Customer action Index
2 mydomain.com/Customer/List controller Customer action List
3 mydomain.com/Customer/List/All No match—too many segments


接收的进来的URL越少的节,将依赖更多的默认值。直到我收到一个没有节的URL,所有的默认值被使用。启动应用程序,导航到根URL,看到运行结果:

 

posted on 2018-05-29 22:14 丹尼大叔 阅读(...) 评论(...) 编辑 收藏

相关文章: