zoukankan      html  css  js  c++  java
  • 理解ASP.NET MVC的路由系统

    引言

    路由,正如其名,是决定消息经由何处被传递到何处的过程。也正如网络设备路由器Router一样,ASP.NET MVC框架处理请求URL的方式,同样依赖于一张预定义的路由表。以该路由表为转发依据,请求URL最终被传递给特定Controller的特定Action进行处理。而在相反的方向上,MVC框架的渲染器同样要利用这张路由表,生成最终的HTML页面并返回URL。所以,理解整个ASP.NET MVC的路由系统,有两个必须出现的关键元素:Controller与Action,有两个方向的操作:传入的路径解析与传出的路径生成。

    以下整理自《Pro ASP.NET MVC 3 Framework》学习笔记。

    路由解析

    一个URL由以下三部分组成:

    http://www.website.com/Admin/Index

    协议                   主机                   查询串

    路由解析,就是要将Admin/Index这部分片段Segment传递给适当Controller的Action进行处理。至于路由何方,则又依赖于预置的路由表项进行指引。所以,在一切开始之前,需要通过添加路由项来完成路由表的配置。

    路由项的添加,由RouteCollection.MapRoute()或RouteCollection.Add()方法实现,并通常在在MVC项目入口Global.asax中的RegisterRoutes()方法中使用。其中,MapRoute()更为常用和直接。

    MapRoute(string routeName,            //路由项的名称,用于路径模式匹配 
             string urlPattern,           //路径模式 
             object defaultProperties,    //路径元素与参数的默认值  
             object constraints,          //条件满足时才适用本路由项的约束 
             string[] namespaces)         //界定Controller位置的命名空间 

    换言之,路由的最终结果就是得到controller.action(parameters)这三项内容。而这三项内容,都将出现在MapRoute()的defaultProperties中。defaultProperties是一个匿名类的对象,其属性包括controller名、action名,以及其他参数在内。

    routes.MapRoute("routeA", 
                    "{controller}/{action}/{page}", 
                    new {controller = "Admin", action = "Index", page = 1}) 

    就象上面这个示例一样,第2个参数urlPattern对应的那个模式串中,{}包围的部分是一个占位符,类似string.Format()中的格式化模式串。整个模式串,用于匹配URL中主机名后的全部内容。而占位符对应的元素,则将完整出现在之后的defaultProperties中,映射到相应的属性上。{}里的这个元素,被称为Segment Variable。

    在用urlPattern匹配URL时,会象方法的参数表一样进行严格匹配。对请求URL没有提供的项,将自动使用defaultProperties中提供的默认值。最终,匹配的结果,是将请求传递到类似这样的一个方法上,匿名类中作为属性的Controller名、Action名以及参数名page都将严格与实际的方法原型匹配(匹配将忽略名称的大小写)。而且匹配的过程呈现出两面性,一面是它只会按Pattern里Segment Variable的数量进行匹配(死板的),另一面是它不会关心每个Segment能否解析出恰当的内容(开明的),比如某个位置本该对应一个整数而不是字符串。

    public class SomeController 
    { 
        public ViewResult SomeAction(int page) { .... } 
    }

    占位片段过多的URL则会失配。正如下表:

    URL                                           controller    action    page 
    http://www.website.com/                         Admin        Index     1 
    http://www.website.com/Product                  Product      Index     1 
    http://www.website.com/Admin/Index              Admin        Index     1 
    http://www.website.com/Admin/Logon              Admin        Logon     1 
    http://www.website.com/Product/List             Product      List      1 
    http://www.website.com/Admin/Index/5            Admin        Index     5 
    http://www.website.com/Admin/Index/5/detail     null         null     null 

    其次,路由表项的排列顺序直接影响路由结果。因为MVC框架按路由项添加的先后顺序进行匹配,而非匹配程度高低,所以越特殊的路由项需要越早被定义。反观URL模式,它将严格按格式串的语义进行解析。如"Prefix/{controller}/{action}",将匹配到类似http://www.website.com/Prefix/Product/List"这样的URL。如果URL指向特定的文件,比如"download/somefile.pdf",同样也可以设置RouteCollection.RouteExistFiles = true,使文件也被路由系统接管,传递给特定的controller。

    除去上述这种在路由中指定参数默认值的方式外,还可以利用UrlParameter.Optional声明可选参数来影响路由。与路由定义中的默认参数将始终存在不同,可选参数意味着仅当请求URL中出现对应于该可选参数的片段时,才会有一个相应的参数被构造并被传递给action,否则就当这个参数从来没有被定义和存在过。

    routes.MapRoute("routeA", 
                    "{controller}/{action}/{page}", 
                    new { controller = "Admin", action = "Index", page = UrlParameter.Optional }) 

    对于上例中的可选参数page,可以在定义action时指定其默认值。如果从默认值的角度看,默认参数与可选参数起到的作用非常近似,但可选参数对于action而言并非固定的存在。

    public class AdminController 
    { 
        public ViewResult Index(int page = 5) { .... } 
    }

    此外,还有可变长度的路由变量,类似于用C#中params关键字定义的可变长度的方法参数。具体地,是在占位符名称前加上*,就象{*arguments}这样。之后,路由框架会按照路径模式对URL逐段进行匹配,并将末端失配的所有片段作为一个由/分隔的串传递给参数arguments。在action内部,可以对arguments进行分割与解析,做出自己需要的其他处理。

    接下来,是路由项中的namespace,该参数确定该路由项在查找controller时可以参考的命名空间。MVC将先在该参数给定的命名空间中查找controller,查找未果后才去其可触及的其他命名空间查找。可以通过设置路由项的DataTokens["UseNamespaceFallback"] = false来强迫只在指定空间查找。

    namespaces参数给出的命名空间,无论其顺序如何,其拥有的controller具有同等的优先权。如果不同空间下有同名controller存在,将直接导致同名冲突。为避免冲突,可以分别为这些冲突的controller所属命名空间定义不同的路由项,并适当考虑将哪一条路由项放在更前面。

    最后还有一个略复杂些的参数constraints,它决定了该路由项适用的条件。若干条件之间,为AND的关系,即须同时满足。约束既可以是简单地利用正则匹配,就象下面这样仅当controller名以H开头,action为Index或About时才适用此路由项。

    MapRoute("MyRoute", 
             "{controller}/{action}/{id}/{*arguments}",
             new { controller = "Home", action = "Index", id = UrlParameter.Optional },
             new { controller = "^H.*", action = "^Index$|^About$" },
             new[] { "SomeNamespace.Controllers" });

    更复杂一些的约束,则要通过实现了IRouteConstraint接口的约束对象来提供,其重点是实现该接口中的bool Match()方法。然后往上面的约束表中简单地加入一段ieConstraint = new AgentIEConstraint()即可实现对浏览器内核的筛选。

    public class AgentIEConstraint : IRouteConstraint
    {
        public bool Match(HttpContextBase context, Route route, string name, RouteValueDictionary values, RouteDirection direction)
        {
            return (context.Request.UserAgent != null)
                   && (context.Request.UserAgent.Contains("IE");
        }
    }

    输出URL

    路由系统的另一重要功能,是生成URL。这主要是通过Html.ActionLink()、Html.RouteLink()、Url.Action()、Url.RouteUrl()等方法实现的,他们作用相当、参数近似,又以ActionLink()生成<a>标签指向特定的action较为常用,RouteLink()则可以直接选择特定路由并指向具体的文件、文件夹等资源。

    尽量避免用手工方式定义URL,这实在太危险!

    ActionLink(string text,             //链接显示的文本
               string action,           //action名称
               string controller,       //controller名称
               string protocol,         //URL 协议,如“http”或“https”
               string host,             //URL中 的主机名
               string fragment,         //URL 片段名称(定位点名称)
               object routeValues,      //一个包含路由参数的对象
               object htmlAttributes)   //一个对象,其中包含要为该元素设置的 HTML 特性

    URL的解析与输出,并不当然的是一个互逆的过程。

    引入Areas进行分区

    分区,是对URL更精细的一种路由手段。

  • 相关阅读:
    打印二叉树中节点的所有祖先
    1.把2叉查找树转换成双向链表
    Linux下tar.xz结尾的文件的解压方法
    Floyd算法
    c缺陷与陷阱笔记-第七章 可移植性代码
    c缺陷与陷阱笔记-第六章 预处理器
    c缺陷与陷阱笔记-第四章 连接
    C语言小程序(四)、杨辉三角
    C语言小程序(三)、判断两个日期之差
    C语言小程序(二)、计算第二天日期
  • 原文地址:https://www.cnblogs.com/Abbey/p/4889417.html
Copyright © 2011-2022 走看看