[译]Asp.net MVC 之 Contorllers(二)

URL路由模块

 书接上回[译]Asp.net MVC 之 Contorllers(一)

URL 路由HTTP模块通过获取 URL,然后调用合适的执行方法处理进来的请求。URL 路由 HTTP 模块取代了旧版本 ASP.NET 的 URL 重写功能。URL 重写的核心包括获取请求、解析原始 URL 以及指导 HTTP 运行时环境服务于“可能相关但不同(possibly related but different)” 的 URL。

取代URL重写

在可读性、搜索引擎优化(SEO,search engine optimization)和程序处理 UR L的数量级之间做出权衡时,URL重写就可以闪亮登场了。思考一下这个URL:

http://northwind.com/news.aspx?id=1234

news.aspx页面集成了检索信息、组织信息以及显示信息的所有逻辑。这个 ID 是 Querystring 中的参数,根据他可以获取特定的消息。对于程序猿,实现这个页面,非常easy。

只需要简单的三个步骤(相当于把大象装冰箱):

  1. 获取到 Query String 中参数 ID 值(打开冰箱门)。
  2. 运行一个查询语句获取结果(把大象放进去)。
  3. 创建一个展示的界面(关上冰箱门)。

对于用户和搜索引擎来说,单纯从URL很难真正的理解这个页面是干什么的,而且很难记清楚网址以及具体需要传的值。

URL重写在两个方面上做了改善:

首先、程序猿可以用一个通用的前端页面(如:news.aspx)显示相关内容。

其次、用户可以请求更加友好的URL,这些URL被程序通过代码自动映射成不那么直观但又便于管理的URL上。

总的来说,URL 重写就实现服务于请求的物理页面与请求URL的解耦。

在 ASP.NET 4 Web Forms 最新版本中,可以使用 URL 路由将传入的 URLs 匹配其他 URLs 而不会产生 HTTP 302 重定向的消耗。然而,在 ASP.NET MVC 中,URL 路由是把传入的 URL 映射到 Controller 类和 Action 方法为目的的。

注 最初开发 URL 路由模块的目的是作为一个 ASP.NET MVC 组件,现在已经是 ASP.NET 平台的一部分,只是 ASP.NET MVC 和 ASP.NET Web Forms 提供的 API 稍有不同。

路由请求

向IIS发出请求的时候,究竟发生了什么呢?

下图给出了在 ASP.NET MVC 和 ASP.NET Web Forms 应用程序中涉及相关的各个步骤如何工作的总体图。

URL路由模块会拦截无法由IIS服务处理的应用程序的任何请求。如果URL是指向一个物理文件(例如,一个ASPX文件) ,那么路由模块将忽略该请求,除非另行配置。在页面处理程序方面,ASP.NET 机制正常处理该请求。

接着,URL路由模块尝试匹配所有应用程序定义的路由的URL。如果匹配到,请求将转到 ASP.NET MVC 的领地,再调用一个控制器类进行处理。如果没有匹配到,请求将被标准的 ASP.NET 运行时以最合适的方式处理,很可能的结果就是返回一个404。

最后,只有符合预定义的 URL 格式(也就是路由)的请求,才被允许享用 ASP.NET MVC 运行时的服务。所有这些请求都被路由到一个共同的序实例化控制器类的HTTP处理程,并调用其中定义的方法。接下来,控制器方法将选择一个视图组件,生成实际的响应。

URL路由模块的内部结构

从实现角度讲,我们应该注意到 URL 路由引擎是一个触发 PostResolveRequestCache 事件的 HTTP 模块。在 ASP.NET 缓存中先检查,如果对于请求没有可用的响应,之后就会触发该事件。

HTTP 模块匹配到用户定义的 URL 路由请求的 URL,并将 HTTP 上下文设置为使用 ASP.NET MVC 标准的 HTTP 处理程序来处理该请求。作为程序猿,不可能直接处理 URL 路由模块。该模块由系统提供,不需要我们特别去配置。我们的责任是提供应用程序支持的路由,以及路由模块实际使用的路由。

应用程序路由

按照设计,ASP.NET MVC 应用程序并没有依赖于物理页面。在 ASP.NET MVC 中,用户请求代理资源(acting on resources)。然而,框架没有规定描述资源和 Action 的语法。表达为"代理资源(acting on resources)",很可能会被认为是 REST。,当然,这样想也不是很离谱。

可以使用 ASP.NET MVC 应用程序中的 REST 方式,ASP.NET MVC 面向他是松耦合的,ASP.NET MVC 承认他的概念,如资源和行为,我们可以随意使用自己的语法来表达和实现资源和行动行为。例如,在一个纯粹的 REST 解决方案,是使用H TTP 谓词来表达行为动作(GET,POST,PUT和DELETE)和通过URL识别资源。可以在在 ASP.NET MVC 中实现一个纯粹的 REST 的解决方案,但需要做一些额外的工作。

通过指定动作行为和资源可以自定义语法,在 ASP.NET MVC 中默认行为是使用自定义语法的 URLs。该语法是以 URL 模式的集合作为表现形式,也称为路由。

URL模式和路由

路由是URL绝对路径样式匹配的字符串,也就是一个没有协议、服务和端口信息的URL字符串。路由可能是一个字符串常量,但很可能还包含一些占位符。

一个简单的路由:

/home/test

路由是一个常量字符串,并且他仅被一个路径是 home/test 的 URL 匹配。然而,大多数时候,我们处理的是包含一个或多个占位符的参数化路由。

请看下面两个例子:

/{resource}/{action}

/Customer/{action}

这两个路由都可以被任何只有两个部分的 URL 匹配。第二个要求第一段是字符串 “Customer”。然而,第一个没有对每段内容做出具体限制。

通常,大括号{}内的占位符被称为 URL 参数。只要 URL 参数是由常量或分隔符隔开,路由就可以有多个 URL 参数。正斜杠(/)字符作为路由各个部分之间的分隔符。占位符的名字(例如,action)是代码在实际 URL 中检索相应段的内容的关键。

下面是ASP.NET MVC 应用程序中默认路由:

{controller}/{action}/{id}

上面路由包含三个占位符,其中由分隔符分开。下面是一个匹配上面路由的一个URL:

/Customers/Edit/ABC

我们可以添加多个路由,并且给路由添加多个占位符,也可以删除该默认路由。

定义应用程序路由

应用程序的路由通常注册在global.asax文件中,他在应用程序启动时被处理。

global.asax文件中处理路由的部分:

    public class MvcApplication : HttpApplication
    {
        protected void Application_Start()
        {
            ...
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ...
        }
    }

RegisterRoutes是RouteConfig类中的一个方法,RouteConfig类一般定义在App_Start文件夹中。当然,我们可以随意命名。下面是这个类的具体实现:

    public class RouteConfig
    {
        public static void  RegisterRoutes(RouteCollection routes)
        {
            ...

            // 路由集合 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }

Application_Start事件处理程序调用了包含所有路由集合的方法 RegisterRoutes。关于 RegisterRoutes 方法的名称这里需要注意一下,他不是死的的,只要认为合适,就可以随意更改他。

为了支持路由,必须添加一个被 ASP.NET 管理的静态路由对象集合。这个集合就是 RouteTable.Routes。通常我们使用 MapRoute 方法来填充这个集合。MapRoute 方法有很多重载方法,大部分时候他们都是行之有效的。然而,他不会让我们配置路由对象每一个可以配置的方面。如果我们需要设置一个路由,但 MapRoute 不支持,那么我们可能要采取下面的代码:

            // 创建一个新的路由,并添加到集合中
            var route = new Route(...);
            RouteTable.Routes.Add("NameOfTheRoute", route);

路由的特点是几个属性(名称,URL格式,默认值,约束,数据标记和路由处理)。我们最常设置的属性基本就是名称、 URL 模式和默认值。

展开默认路由方法的代码:

            // 路由集合 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

第一个参数是路由的名字,每一个路由都应该有一个唯一的名字。第二个参数是路由模式/格式。第三参数是默认路由参数值的对象。

注意,UR L甚至可以使用非完整格式匹配的模式。让我们思考下这个 URL,HTTP:// yourserver.com。乍一看,这个URL不会被路由匹配。但是,如果URL参数有默认值,那么默认值得部分被认为是可选的。因此,对于前面的例子,当请求根 URL 的时候,该请求就是调用 Home 控制器上的 Index 方法。

处理路由

当尝试匹配一个请求 URL 到定义的路由的时候,ASP.NET URL 路由模块采用了一些规则。最重要的规则是,路由必须按照定义在 Global.asax 中的顺序进行检查。

为了确保路由是按照正确的顺序被处理,必须按照具体性递减的顺序。不管怎么样,我们必须注意, 路由匹配是在整个路由集合中一直尝试匹配。 当匹配到以后,将停止匹配,不会继续去匹配更多的路由。这样的话,把新添加的路由放在路由列表的尾部,可能不会起作用,也可能引起一些麻烦。另外还要注意,如果在列表的顶部放置一个可以捕捉全部格式的路由,那么,所有其他的路由将会被全部忽略。

注:路由顺序是小事小的不值得一体,但他影响却能大的无法想象。

在不考虑匹配顺序的情况下,其他还有什么可能影响匹配URL路由处理的因素。如前所述,提供路由的默认值。如果请求缺少参数,则会使用默认值,默认值就是简单地自动分配给定义的占位符。思考下面两个路由:

{Orders}/{Year}/{Month}{Orders}/{Year}

如果赋值给第一个路由中{Year}和{Month},那么由于默认值的原因第二个路由将永远不会被匹配到,无论是否指定具体年和月,第一个路由总是被成功匹配。

而结尾的斜线(/)也是一个陷阱。路由{Orders}/{Year} 和{Orders}/{Year}/ 两个完全不是一回事。

另一个影响URL路由匹配的因素是约束列表,我们可以选择给路由定义。路由约束就是要求给定的 URL 参数必须遵循的 URL 匹配路由的附加条件。URL 不仅应与 URL 模式兼容,它也需要包含兼容的数据。一个约束可以以各种方式来定义,其中包括通过正则表达式。

带约束路由的例子:

            // 带约束的路由集合
            routes.MapRoute(
                name: "ProductInfo",
                url: "{controller}/{productId}/{locale}",
                defaults: new {controller = "Product", action = "Index", locale = "en-us"},
                constraints: new {productId = @"\d{8}", locale = "[a-z]{2}-[a-z]{2}"});

特别指出,该路由要求 PRODUCTID 占位符必须是正好8个数字的数字序列,而 local 的占位符必须是用破折号分开的一对双字母字符串。约束不能保证所有无效的产品 ID 和 local 代码都被拦截,但至少做了大量的拦截工作。

路由处理程序

路由定义了一系列最低要求的规则,根据路由模块决定请求的URL是否可以访问应用程序。最终决定如何重新映射所请求的 URL 完全是另一个组件。这就本段的主角路由处理程序。路由处理程序是处理匹配给定路由任何请求的对象。它存在的唯一目的是返回 HTTP 处理程序,该 HTTP 处理程序将服务于任何匹配的请求。

从技术上讲,路由处理程序是一个实现了IRouteHandler的类,接口定义如下:

        public interface IRouteHandler
        {
            IHttpHandler GetHttpHandler(RequestContext requestContext);
        }

定义在System.Web.Routing命名空间下,RequestContext封装了请求 HTTP 上下文,再加上所有可用的具体路由的信息,如路由对象本身、URL参数以及约束。这些数据是被分组到一个RouteData对象。下面是RequestContext类的签名:

public class RequestContext
{
    public RequestContext(HttpContextBase httpContext, RouteData routeData);

    public HttpContextBase HttpContext { get; set; }

    public RouteData RouteData { get; set; }
}

ASP.NET MVC 框架并没有提供很多内置的路由处理程序,而这可能是一个需要使用自定义的并非公用的路由处理程序的签名。然而,在需要的情况下,可以利用这个扩展功能。会在后面的章节说自定义路由处理程序时,并提供一个例子。

处理物理文件请求

路由系统是否具有处理匹配物理文件的请求,是有助于建立一个成功的URL到路由匹配的路由系统的另一个配置方面。

默认情况下,ASP.NET 路由系统忽略可以被映射到存在于服务器上的物理文件的 URL 请求。需要注意,如果请求文件在服务器真实存在,即便与路由请求相匹配,路由系统仍然会忽略该请求。

如果需要匹配物理文件,可以通过设置路由来强制路由系统处理所有请求,在 RouteCollection 对象中将 RouteExistingFiles属性设置为 True,如下所示:

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.RouteExistingFiles = true;
        }

注意:通过路由来处理所有的请求,可能会在 ASP.NET MVC 应用程序中产生一些问题。例如,将前面的代码添加到一个 ASP.NET MVC 应用程序的 Global.asax.cs 文件,然后运行,当访问default.aspx页面时,会出现一个 HTTP 404 错误。

防止路由定义的URL

在ASP.NET URL路由模块没有限制我们去维护可接受的URL模式列表;我们也可以保留某些 URL 关闭的路由机制。

我们可以通过两个步骤来防止来自于处理某些URL的路由系统。

首先,为这些URL定义模式并保存到的路由中。

接下来,将该路由链接到一个特殊的路由处理程序 StopRoutingHandler 类。它所做的就是调用GetHttpHandler方法时抛出一个NotSupported异常。

例如,下面的代码说明路由系统将忽略任何axd的请求:

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
        }

所有 IgnoreRoute 的确关联到 StopRoutingHandler 路由处理程,来处理路由构建指定的URL模式。

最后,解释一下请求URL中的{ * PathInfo}的占位符。标记 PATHINFO 仅仅代表 URL 中在 .axd 后面的所有内容的一个占位符。星号( * ) 表示最后的参数应该匹配 UR L的其余部分。换句话说,任何跟随在 .axd 后面的字符串都是 PATHINFO 参数匹配的范围。这些参数被称为完全捕获参数。

属性路由

NuGet 的 ASP.NET MVC 5 中包含 AttributeRouting 。属性路由是所有有关使用属性直接在控制器的Action方法上定义的路由。正如前面所说,经典路由是在应用程序启动时基于在Global.asax中建立的约定。

任何时候的请求,URL都是与路由注册的模板中相匹配的。如果匹配,就可以确定请求的相应的控制器和action方法。如果没匹配,该请求将被拒绝,结果通常是404 消息。现在,在大型应用程序中,甚至是在具有很强的REST特点的中型应用中,路由的数量可能相当可观,随随便便就会定义出上百个路由。可能很快就会发现,经典路由变得有些力不从心。出于这个原因,AttributeRouting项目已启动,现已集成在ASP.NET MVC 5 中,甚至在 Web API 也有集成,将在后面章节讨论。

        [HttpGet("orders/{orderId}/show")]
        public ActionResult GetOrderById(int orderId)
        {
            ...
        }

上面代码设置方法 GetOrderById 在通过 HTTP GET 调用,并且 URL 模板匹配指定模式时,该方法是可以被访问的。路由参数(orderId 标记) 必须与定义在方法签名中一个参数相匹配。有更多的属相可用(对于每个 HTTP 谓词),但这已经是路由属性要点。了解更多信息(如,配置),可以参考http://attributerouting.net,在 NuGe t包中已经集成到 ASP.NET MVC。 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

关于WCF的一个非常“无语”的BUG!

这确实是一个让人觉得“无语”的BUG,甚至让我觉得微软在故意和我们开玩笑。这个问题在我刚刚接触WCF的时候就遇到过,换言之,这个问题一直存在于.NET 3.0、...

2037
来自专栏林德熙的博客

WPF 使用RPC调用其他进程

如果在 WPF 需要用多进程通信,一个推荐的方法是 WCF ,因为 WCF 是 RPC 计算。先来讲下 RPC (Remote Procedure Call) ...

1561
来自专栏IMWeb前端团队

浏览器中的ECMAScript模块(译)

本文作者:IMWeb zzbozheng 原文出处:IMWeb社区 未经同意,禁止转载 原文:https://jakearchibald.com/20...

2128
来自专栏草根专栏

用ASP.NET Core 2.0 建立规范的 REST API -- 预备知识 (2) + 准备项目

3130
来自专栏Ken的杂谈

基于GitLab的Code Review教程

也就是说,使用GitLab进行Code Review就是在分支合并环节发起Merge Request,然后Code Review完成后将代码合并到目标分支。

1.5K3
来自专栏林德熙的博客

Roslyn 如何在 Target 引用 xaml 防止文件没有编译

在使用新的项目格式,可以使用 Target 添加项目,但是有一些项目需要在合适的时候添加,如果添加早了,那么会让用户看到这些文件,如果添加的时间是在引用编译之后...

881
来自专栏技术点滴

远程线程注入引出的问题

远程线程注入引出的问题 一、远程线程注入基本原理 远程线程注入——相信对Windows底层编程和系统安全熟悉的人并不陌生,其主要核心在于一个Windows AP...

28710
来自专栏张善友的专栏

.NET Migration工具

Migration是一种分布环境下的数据库同步工具,出现在Ruby on Rail框架里,MigratorDotNet是一个.NET类似于Ruby on Rai...

2279
来自专栏静默虚空的博客

Eclipse 实用技巧

代码智能提示 Java智能提示 Window -> Preferences -> Java -> Editor -> Content Assist -> Aut...

2177
来自专栏转载gongluck的CSDN博客

程序的入口

操作系统装载应用程序后,做完初始化工作就转到程序的入口点执行。程序的默认入口点由连接程序设置, 不同的连接器选择的入口函数也不尽相同。在VC++下,连接...

3769

扫码关注云+社区

领取腾讯云代金券