2.6 路由

ASP.NET MVC框架中提供的URL路由机制能够使URL不必映射到应用程序的特定物理文件中,因此可以在Web应用程序中使用易于描述用户行为并且更易于为用户理解的URL。

2.6.1 理解路由

ASP.NET路由只是一个模式匹配系统。开始时,应用程序使用路由(RouteTable)表注册一种或者多种路由(Route)模式,告诉路由系统如何处理这些与模式匹配的请求。路由引擎在运行时接收到请求以后,它就会根据事先注册的URL模式匹配当前请求的URL,如图2-15所示。当路由引擎在路由表里发现匹配的模式时,它就会把请求转发给特定的处理器来处理请求,如果找不到匹配的任何路由,路由引擎则无法知道如何处理这个请求,就会返回404状态错误码。

图2-15 路由请求及处理过程

比如在创建的第一个应用程序案例中,只需要在浏览器地址栏里输入类似于下面的URL:

        http://localhost:1885/Home/Index

那么,ASP.NET MVC框架就会自动调用控制器Home的行为Index。而行为Index将返回一个特定的视图,或者将重定向到另一个控制器。之所以会产生以上描述的现象,完全是由于ASP.NET MVC框架的路由机制在发挥作用。ASP.NET MVC框架利用路由使URL与控制器和行为对应起来。路由解析包含在URL中的变量,并自动把变量作为参数传递给控制器的行为。

2.6.2 路由表

对于一个Web应用程序来说,访问所有的页面采用的URL不可能具有相同的模式,与之匹配的路由不可能是唯一的。一个Web应用程序通过RouteTable类型的静态只读属性Routes维护一个全局的路由表,该属性返回一个RouteCollection对象,我们将这个对象称为路由表。以下代码展示了路由表中配置的3种路由规则。

        public static void RegisterRoutes(RouteCollection routes)
        {
            //路由规则1:忽略的路由规则
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            //路由规则2:自定义路由
            routes.Add(new Route(
                "User/{action}/{userId}"
                , new MvcRouteHandler()
            ));
            //路由规则3:默认路由
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller ="Home",
                    action ="Index", id =UrlParameter.Optional }
            );
        }

2.6.3 添加路由

当创建一个ASP.NET MVC应用程序后,系统会调用全局文件Global.asax中的Application_Start()事件方法,此方法随后会调用RouteConfig.RegisterRoutes(RouteTable.Routes)方法创建路由表(RouteTable)。而项目中默认的路由注册在RouteConfig.cs文件中,代码如下。

Global.asax文件的Application_Start()事件代码使用RouteConfig.RegisterRoute方法注册系统中的所有路由:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web;
        using System.Web.Mvc;
        using System.Web.Routing;

        namespace LMS
        {
            public class MvcApplication : System.Web.HttpApplication
            {
                protected void Application_Start()
                {
                    AreaRegistration.RegisterAllAreas();
                    RouteConfig.RegisterRoutes(RouteTable.Routes);  //注册所有路由,
                                                                    含默认路由
                }
            }
        }

RouteConfig.cs文件的代码使用MapRoute()方法注册默认路由:

        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Web;
        using System.Web.Mvc;
        using System.Web.Routing;

        namespace LMS
        {
            public class RouteConfig
            {
                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.MapRoute()方法注册了默认路由Default,该路由将URL的第一部分映射到控制器名,将URL的第二部分映射到控制器的行为,将第三部分映射到id参数。假设用户在浏览器的地址栏输入了下面的URL:

        /Home/Index/1

默认的路由将这个URL映射为下面的参数:

        Controller =Home
        Action =Index
        Id =1

因此,当请求“URL:/Home/Index/1”时,系统将会执行执行下面的代码:

        HomeController.Index(1)

路由的添加是通过把路由添加到RouteTable类的静态Route属性中实现的。其中RouteTable.Routes中的Routes属性是系统中的RouteCollection对象,存储了ASP. NET应用程序所有的路由,之所以把路由添加到Global.asax文件中的Application_Start()事件处理器中进行,是因为这样可以确保当应用程序启动时路由是可用的。

路由的添加可以利用RouteCollection类的Add()方法和MapRoute()方法来完成。使用Add()方法添加路由的代码如下:

        public static void RegisterRoutes(RouteCollection routes)
        {
          routes.Add(new Route(
                    "User/{action}/{userId}"       //自定义路由格式
                    ,new MvcRouteHandler()          //默认的路由处理代理
                ));
        }

上面的代码中使用Add()方法添加路由并需要新建Route对象,而MapRoute()方法简化了路由添加的语法,不必新建Route对象,只需要添加Route对象的属性,它会根据设置的属性自动创建Route实例。

在添加路由时有一定的顺序规则,因为路由匹配将从集合汇总的第一个路由进行,直到最后一个路由。当发生一个匹配时,则不会再去匹配其他的路由,一般情况下,默认的路由是路由表中最后一个路由。

2.6.4 路由格式

在一个路由中,需要使用一对{}来定义占位符(称为URL参数),即需要把它们包括在括号中。当分析URL时,字符“/”被解释成分隔符,并且将从分隔符之间提取出来的值赋值给占位符。在路由定义中没有包含在{}或[]内的信息被视为一个常量。表2-3展示了一些有效路由模式与该模式匹配的URL请求的例子。

表2-3 有效的路由模式

2.6.5 默认路由

ASP.NET MVC项目模板包括预先配置好的URL路由,它们定义在ASP.NET应用程序中的RouteConfig.cs文件内。这些默认的路由使得在大多数场合下不必显式地进行路由配置即可进行ASP.NET MVC应用程序项目的开发。默认路由的代码如下:

        public class RouteConfig
        {
            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 }
                );
            }
        }

从上面的代码可以看出,系统自动生成了一个默认的路由规则,默认的路由名称为Default,默认路由也都提供了默认值,即路由中的controller、action、id都可以根据情况使用默认值,而不需要显式指定每个值。表2-4中列举了默认路由的一些默认参数值,当然我们可以在MapRoute()方法内修改属于自己的默认参数。

表2-4 默认路由使用举例

图2-16中地址栏的URL为http://localhost:1885/,这是调用了默认路由,省略了“Controller="Home", Action="Index"”的结果。

图2-16 默认路由的执行结果

2.6.6 在URL模式中处理可变数量的段

有时我们需要处理包含可变数量的URL片段的URL请求。定义路由时,可以指定URL是否具有模式中更多的段,是否将额外的段视为最后一个段的一部分。若要以此方式处理额外的段,可以用星号(*)标记最后一个参数。该参数称为“可用于放置各种信息的”参数。具有全部捕捉参数的路由也将与那些不包含最后一个参数的任意值的URL相匹配。下面的示例演示一个与未知数量的段匹配的路由模式。

        query/{queryname}/{*queryvalues}

ASP.NET路由处理URL请求时,在示例中演示的路由定义得到表2-5列出的结果。

表2-5 片段匹配的路由模式

2.6.7 添加路由约束

除了按照URL中的参数数量将URL请求匹配到路由定义中,还可以指定参数中的值满足特定的约束。如果一个URL包含路由的约束以外的值,则该路由不用于处理请求。添加约束以确保URL参数包含将在应用程序中起作用的值。

约束是通过使用正则表达式或使用实现IRouteConstraint接口的对象来定义的。将路由定义添加到Routes集合时,同时也通过创建一个包含验证测试的RouteValueDictionary对象添加了约束。字典中的关键字标识约束适用的参数。字典中的值可以是表示正则表达式的字符串,也可以是实现IRouteConstraint接口的对象。

提供字符串后,路由将视字符串为正则表达式,并通过调用Regex类的IsMatch方法检查参数值是否有效,而正则表达式视为不区分大小写。

提供IRouteConstraint对象后,ASP.NET路由将通过调用IRouteConstraint对象的Match方法检查参数值是否有效。Match方法返回一个布尔值,该值指示参数值是否有效。

通过使用正则表达式可以规定参数格式,比如id参数只能为4位数字:

        new { id =@"\d{4}"}

通过实现IRouteConstraint接口也可以限制请求的类型。因为System.Web. Routing中提供了HttpMethodConstraint类,这个类实现了IRouteConstraint接口。我们可以通过为RouteValueDictionary字典对象添加键为httpMethod、值为一个HttpMethodConstraint对象的操作,来为路由规则添加HTTP谓词的限制,比如限制一条路由规则只能处理GET请求:

        httpMethod=new HttpMethodConstraint( "GET", "POST" )

完整的代码如下:

        routes.MapRoute(
            "Default",                         //路由名称
            "{controller}/{action}/{id}"       //带有参数的路由
            new {
                controller ="Home",
                action ="Index",
                id ="" },                       //路由默认的参数值
            new { id =@"\d{4}",                 //约束参数id的规则
                httpMethod =new HttpMethodConstraint("GET", "POST")}
        );

以下代码是自定义的一些路由规则,用于约束规则中的参数

        public static void RegisterRoutes(RouteCollection routes)
        {
            //忽略对.axd文件的路由,也就是与WebForm一样,直接去访问.axd文件
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            //URL匹配
            //Details/list/Chongqing/100,200-3
            routes.MapRoute(
                "Details",
                "Details/{action}/{city}/{price}-{star}",
                new {controller ="Details",            //指定路由参数的默认值
                    action ="list",
                    city ="Chongqing",
                    price ="-1, -1",
                    star ="-1"},
                new {city =@"[a-zA-Z]*",             //设置参数的规则
                    price =@"(\d)+\, (\d)+",
                    star ="[-1-5]"}
            );
            // URL匹配
            // Details/所有匹配
            routes.MapRoute(
                "Details",
                "Details/{*values}",           //匹配所有的表达式
                new { controller ="Details", //指定路由参数的默认值
                    action ="default",
                    id ="" }
            );