ASP.NET MVC 的“为什么”和“怎么做”:第三部分






4.65/5 (24投票s)
ASP.NET MVC 控制器和路由的基本理念、优点以及控制器验证。
引言
承接我们上一篇关于 第二部分 的讨论,希望现在大家对 Model 已经有了一定的了解。现在我们可以转向下一个主题,即 Controllers(控制器)和我的个人最爱——Routing(路由)。我喜欢将 Controllers 和 Routing 放在同一篇文章中介绍,因为我认为这会更方便读者。和往常一样,我喜欢跳过“怎么做”,而是专注于“为什么”。
为什么需要控制器 (Why Controllers)
控制器的作用是避免请求混乱。没有控制器,事情会变得一团糟,这不仅适用于 MVC,在现实生活中也是如此。现在想想一个现实生活中的例子。在机场,有数百架飞机使用跑道,但机场会为每架飞机提供独立的跑道吗?不会,飞机共享同一条跑道。想想如果没有对哪架飞机何时使用跑道进行控制,情况就会像下图一样混乱。
为了避免这种混乱,机场需要 ATC(空中交通管制)。正如维基百科所说:
ATC 的主要目的是防止碰撞、组织和加快交通流量,并为飞行员提供信息和其他支持。
与 ATC 类似,MVC 中也有一个 Controller(控制器)。控制器控制应用程序逻辑,并充当视图和模型之间的协调者。
这个过程非常简单。当用户的请求到达 MVC 框架时,它首先会进入控制器。此时,控制器的职责是确定应该调用哪个模型,然后调用该模型中的相应函数。调用函数后,它会捕获结果。
现在的问题可能是,这是 MVC 中才引入的新概念吗?答案是否定的。如果您阅读了第一篇 文章,我们非常熟悉的 Web Forms 也使用了类似的概念。Web Forms 使用 Page Controller 模式来处理定向到特定网页的所有 HTTP 请求。MVC 在这方面超越了传统的 Web Forms,它将控制流结构化为控制器,使应用程序逻辑更简单、更易于测试。
当我们讨论控制器处理用户请求时,路由(Routing)就发挥作用了。路由在 MVC 应用程序中预先配置,用于调用控制器中定义的动作方法。
路由和控制器 (Routing and Controller)
您可能见过旅行者用来识别、导航和组织县/州内路线的高速公路标志或路线标记。
就像高速公路标志指引旅行者一样,ASP.NET 路由将传入的请求导向相应的控制器动作。
顾名思义,路由是一个处理器,实际上是一个处理请求的类,例如 MVC 应用程序中的控制器。ASP.NET 路由模块负责将传入的浏览器请求映射到特定的 MVC 控制器动作。
路由使用 URL 模式来识别正确的控制器和动作。MVC 应用程序中路由的典型 URL 模式是:domain/{controller}/{action}/{id}。
当收到请求时,它会被路由到 UrlRoutingModule --> MvcHandler。MvcHandler HTTP 处理程序确定要调用哪个 {controller}。URL 中的 action 值决定调用哪个 {action} 方法,如果指定了 {id}
,则会带上 ID 参数。
如何选择控制器 (How to Choose Controllers)
通常,ASP.NET MVC 控制器动作在动作完成时会阻塞 ASP.NET 线程池。这称为同步编程模型。但在某些情况下,我们希望动作不会阻塞线程池中的线程。对于这些场景,我们可以使用 .NET Framework 4.5 中引入的异步控制器。
使用异步控制器的好处如下:
- 对于 I/O、网络绑定或长时间运行且可并行化的请求,编写异步方法将提高应用程序的响应能力。
- 用户比同步请求更容易取消。
现在您可能会想,这是什么鬼东西?什么时候使用同步控制器,什么时候使用异步控制器?
别担心,您不必盲目选择使用异步还是同步。适合同步动作的场景:
- 简单且运行时间短的操作。
- 简单比效率更重要的场景。
- CPU 密集型操作。
- 当动作必须执行多个独立的操作时。
适合异步动作的场景:
- 长时间运行的操作导致性能瓶颈。
- 网络或 I/O 密集型操作。
- 当应用程序需要用户能够取消长时间运行的操作时。
- 当 IIS 需要通过异步方法处理更多请求时。
请注意:如果控制器动作是 CPU 绑定的,使其异步调用不会带来任何好处,反而可能为这些操作增加开销。因为异步控制器不会改变请求的执行时间,它只是释放执行请求的线程,使其可以重新分配到 ASP.NET 线程池中。
如何定义控制器 (How to Define Controllers)
物理上,控制器只是另一个类,它必须以“Controller”后缀结尾,并且可以暴露控制器动作。当用户在浏览器中输入 URL 时,MVC 应用程序会使用路由规则解析 URL 并确定控制器的路径。任何被定义为公共的方法都被认为是动作方法。控制器动作还需要满足一些额外的要求。
- 用作控制器动作的方法不能被重载。
- 控制器动作不能是静态方法。
如何定义自定义路由 (How to Define Custom Routing)
任何派生自 ControllerBase
类且名称以“Controller”结尾的类,都不需要任何自定义路由声明。但您也可以定义自己的路由。自定义路由约束使您能够在满足某个自定义条件时才匹配路由。如果您想在 MVC 应用程序中添加自定义路由,可以在 Global.ascx 中使用 MapRoute(RouteCollection, String, String)
方法。
定义路由时,您可以为参数分配默认路由值。如果 URL 中不存在该参数的值,则会使用默认值。
测试控制器 (Testing Controllers)
如果我们不测试控制器的行为,使用 MVC 的初衷就无法实现。ASP.NET MVC 控制器不过是简单的类,控制器动作不过是简单的方法。因此,这种方法使编写测试用例变得容易得多。
我们可以对相应的控制器进行两种类型的测试:
- 控制器动作是否返回了正确的动作结果。
- 是否为视图结果返回了正确的视图。
作为示例,让我们为HomeController 编写一个单元测试。这是我们自己的默认 HomeController:
为了测试 HomeController 控制器的动作,我们所需要做的就是使用默认构造函数创建一个新实例,并像下面这样调用该动作。
上面展示的测试可能无法满足或涵盖控制器单元测试的每一个方面,但作为开始已经不错了。高级读者可以查看下一节中的参考资料。我希望能够写一篇完整的文章来介绍控制器的各种测试用例。