第4章 使用Razor Pages创建网站(ASP.NET Core in Action, 2nd Edition)

发布时间 2023-04-10 11:52:14作者: 码农小白修炼记

本章重点 (请点击这里阅读其他章节

  • Razor Pages 和模型-视图-控制器(MVC)设计模式简介
  • 在 ASP.NET Core 中使用 Razor Pages
  • 在 Razor Pages 和 MVC 控制器之间进行选择
  • 使用 Action 结果控制应用程序流

通过第3章您已经了解了中间件管道,它定义了 ASP.NET Core 应用程序如何响应请求。另外,在将请求传递到管道中的下一个中间件之前,每个中间件都可以修改或处理传入的请求。

在 ASP.NET Core Web 应用程序中,中间件管道通常包括 EndpointMiddleware。这往往是应用程序通过调用各种各样的类,实现大部分逻辑的地方。它也是用户与应用程序交互的主要入口。它通常采用以下三种形式之一:

  • 为用户直接使用而设计的 HTML Web 应用程序——如果应用程序像传统 Web 应用程序一样直接由用户使用,那么 Razor Pages 则负责生成与用户交互的网页。它处理 URL 请求,接收使用表单发布的数据,并生成用户用于查看和导航应用程序的 HTML。
  • 为另一台机器或代码使用而设计的 API——Web 应用程序的另一种主要作用是提供为后端服务器进程、移动应用程序或客户端框架或者构建单页应用程序(SPA)所使用的 API。在这种情况下,应用程序以机器可读格式(如 JSON 或 XML )提供数据,而不是向用户展示的 HTML 输出。
  • 一个 HTML Web 应用程序和一个 API——同时满足这两种需求的应用程序。这可以让您在应用程序中共享逻辑的同时满足更广泛的客户需求。

在本章中,您将了解 ASP.NET Core 如何使用 Razor Pages 来处理第一个选项,即创建服务器端呈现的 HTML 页面。您将首先看到模型-视图-控制器(MVC)设计模式,以了解通过使用它可以获得的好处,并了解为什么它被如此多的Web框架所采用以构建可维护应用程序的模型。

接下来,您将了解 MVC 设计模式如何应用于 ASP.NET Core。MVC 模式是一个广泛应用于各种场景的概念,但在 ASP.NET Core 中的应用主要是作为 UI 抽象。您将看到 Razor Pages 是如何实现 MVC 设计模式的,它是如何在 ASP.NET Core MVC 框架之上构建的,并比较这 Razor Pages 和 MVC 两种方法的区别。

接下来,您将看到如何将 Razor Pages 添加到现有应用程序中,以及如何创建第一个 Razor Page。您将学习如何定义应用程序接收请求时要执行的页面处理程序,以及如何生成可用于创建要返回的 HTTP 响应的结果。

在本章中,我不会介绍如何创建 Web API。Web API 通常使用 ASP.NET Core MVC 框架,但它们的使用方式与 Razor Pages 略有不同。它们不返回直接显示在用户浏览器中的网页,而是返回格式化后的数据以供代码使用。Web API 通常用于为移动 App、Web 应用程序或其他服务器应用提供数据,并且遵循相同的通用MVC模式。您将在第9章中了解如何创建 Web API。

注:本章是 ASP.NET Core 中 Razor Pages 和 MVC 的第一章。正如我已经提到的,这些框架通常负责处理应用程序的所有业务逻辑和 UI 代码,因此,可能并不奇怪,它们包含的内容很宠大而且有些复杂。接下来的五章会讨论构成 MVC 和 Razor Pages 框架的 MVC 模式的不同方面。

在本章中,我将尝试为您提到后面章节才会详细介绍的很多主题,但您可能会发现,在这个阶段,有些行为感觉有点像魔术。试着不要太在意所有部件是如何结合在一起的;专注于所处理的具体概念。当我们在本书的其他部分中介绍相关细节时,这一切都将变得清晰。

4.1 Razor Pages 简介

Razor Pages 编程模型在 ASP.NET Core 2.0 中引入,作为构建服务器端呈现的"基于页面"网站的一种方式。它建立在 ASP.NET Core 基础设施之上,提供了一种更简化的体验,并尽可能使用约定来减少所需的样板代码和配置的数量。

定义:基于页面的网站是一个用户在多个页面之间浏览、将数据输入表单并生成内容的网站。这与游戏或单页应用程序(SPA)等应用程序形成了鲜明对比,后者在客户端具有很强的交互性。

在第2章中,您已经看到了 Razor Page 的一个非常基本的示例。在本节中,我们将从稍微复杂一点的 Razor Page 开始,以更好地理解 Razor Page 的总体设计。我将展示

• 典型 Razor Page 的示例
• MVC 设计模式及其如何应用于 Razor Pages
• 如何将 Razor Pages 添加到应用程序

在本节结束时,您应该能够很好地了解 Razor Pages 背后的全部设计,以及它们与 MVC 模式的关系。

4.1.1 探索典型的 Razor Page

在第二章中,我们看到了一个非常简单的 Razor Page。它不包含任何逻辑,只是呈现了关联的 Razor View。如果您正在构建一个以内容呈现为主的营销网站,这种模式可能很常见,但更常见的是,Razor Pages 将包含一些逻辑,从数据库加载数据或使用表单允许用户提交信息。

为了让您更了解典型 Razor Pages 的工作原理,在本节中,我们将简要介绍一个稍微复杂一些的 Razor Page。此页面取自待办事项列表应用程序,用于显示给定类别的所有待办事项。现在我们还没有开始关注 HTML 的生成,所以下面的列表只显示 Razor Page 的 PageModel 代码。

//ToDoService是使用依赖注入在模型构造函数中提供的 public CategoryModel(ToDoService service) { _service = service; } public ActionResult OnGet(string category) //OnGet处理程序接受一个参数category。 { Items = _service.GetItemsForCategory(category); //处理程序调用ToDoService以检索数据并设置Items属性。 return Page(); //返回一个PageResult,表明应呈现Razor视图 } public List<ToDoListModel> Items { get; set; } //Razor视图在渲染时可以访问Items属性。 }

该示例虽然相对简单,但与第2章中的基本示例相比,它还是展示了一些新的功能:

  • 页面处理程序 OnGet 方法接受 category 参数。Razor Page 基础结构使用来自传入请求的值在一个称为模型绑定的过程中自动填充此参数。我在第6章中详细讨论了模型绑定。
  • 处理程序不直接与数据库交互。相反,它使用提供的 category 值与 ToDoService 进行交互,ToDoService 通过依赖注入作为构造函数的参数注入。
  • 处理程序在方法末尾返回 Page(),以指示应呈现关联的 Razor View。在这种情况下,return 语句实际上是可选的;按照惯例,如果页面处理程序是一个 void 方法,Razor 视图仍将被呈现,就像您在方法末尾调用了 return page() 一样。
  • Razor View 可以访问 CategoryModel 实例,因此它可以访问处理程序设置的 Items 属性,并使用这些 Items 来构建最终发送给用户的 HTML。

清单4.1的 Razor Page 中的交互模式显示了一种常见的模式。页面处理程序是 Razor Page 的中央控制器。它接收用户的输入(category参数),调用应用程序(ToDoService)的“大脑”,并将数据(通过Items属性)传递给 Razor View,Razor 将生成 HTML 响应。这看起来与模型-视图-控制器(MVC)设计模式类似。

根据您在软件开发方面的背景,您可能以前遇到过某种形式的 MVC 模式。在 Web 开发中,MVC 是一种常见的模式,用于 Django、Rails 和 Spring MVC 等框架中。但由于 MVC 是一个广泛的概念,您可以在从移动应用程序到富客户端桌面应用程序的所有应用程序中找到 MVC。希望这表明了如果正确使用该模式可以带来的好处!在下一节中,我们将了解 MVC 模式的一般情况,以及 ASP.NET Core 如何使用它。

4.1.2 MVC 设计模式

MVC 设计模式是设计具有 UI 的应用程序的常见模式。最初的 MVC 模式有许多不同的解释,每一种解释对于这种模式的侧重点可能稍有不同。例如,最初的 MVC 设计模式是使用在富客户端图形用户界面(GUI)应用程序而不是 Web 应用程序,因此它使用了与 GUI 环境相关的术语和范例。不过,从根本上讲,该模式旨在将数据的管理和操作与其可视化表示分开。

在深入研究 MVC 设计模式本身之前,让我们考虑一个典型的请求。想象一下,应用程序的用户从显示待办事项列表类别之前发出 Razor Page 请求。图4.1显示了 Razor Page 如何处理请求的各个方面,并结合各个方面生成最终响应。

   图4.1请求 Razor Pages 应用程序的待办事项列表页面。不同的“组件”处理请求的每个方面。

通常,MVC 设计模式由三个“组件”组成:

  • 模型——这是需要显示的数据,即应用程序的全局状态。它可以通过清单4.1中的 ToDoService访问。
  • 视图——显示模型提供的数据的模板。
  • 控制器——更新模型并向视图提供显示数据。此角色由 Razor Pages 中的页面处理程序承担。这就是清单4.1中的 OnGet 方法。

MVC 设计模式中的每一个组件都负责整个系统的一个方面,当它们结合起来时,可以用来生成 UI。待办事项列用例将 MVC 视为使用 Razor Pages 的 Web 应用程序,这时请求也可以理解为在桌面 GUI 应用程序中单击按钮。

通常,应用程序响应用户交互或请求时的事件顺序如下:

  1. 控制器(Razor Page处理程序)接收请求。
  2. 根据请求,控制器或者使用注入的服务从应用程序模型获取请求的数据,或者更新构成模型的数据。
  3. 控制器选择要显示的视图,并将模型的表示传递给它。
  4. 视图使用模型中包含的数据来生成 UI。

当我们以这种格式描述 MVC 时,控制器(Razor Page 处理程序)充当交互的入口点。用户与控制器通信以发起交互。在 Web 应用程序中,这种交互采用 HTTP 请求的形式,因此当接收到对 URL 的请求时,控制器会处理它。

根据请求的性质,控制器可以采取各种行动,但关键点是,这些行动是使用应用程序模型进行的。这里的模型包含应用程序的所有业务逻辑,因此它能够提供请求的数据或执行指定的操作。

注:在MVC的描述中,模型被认为是一个复杂的怪物,包含了如何执行动作的所有逻辑,以及所有的内部状态。从这个概念上理解,Razor Page PageModel 类不是我们所讨论的模型!非常不幸,与所有软件开发一样,命名确实是个难题。

考虑查看电子商务应用程序的产品页面的请求。控制器将接收请求,并定位属于应用程序模型的某个产品服务。当然,这可能仅仅是从数据库中获取所请求产品的详细信息,并将其返回给控制器。

或者,设想一个控制器接收到向用户的购物车添加产品的请求。控制器将接收请求,并且很可能会调用模型上的方法来请求向购物车添加产品。然后,模型将更新用户购物车的内部表示,例如,向保存用户购物车数据的数据库表中添加新行。

提示:您可以将每个Razor Page处理程序视为专注于单个页面的小型控制器。每个Web请求都是对生成响应的控制器的一个独立调用。尽管有许多不同的控制器,但处理程序都与同一个应用程序模型交互。

模型更新后,控制器需要决定生成什么响应。使用 MVC 设计模式的优点之一是,表示应用程序数据的模型与称为视图的数据的最终表示分离。控制器负责决定响应是否应生成 HTML 视图,是否应向用户发送一个新的页面,或者是否应返回错误页面。

模型独立于视图的优点之一是它提高了可测试性。UI 代码通常很难测试,因为它依赖于用户和环境,任何编写过模拟用户单击按钮并输入表单的 UI 测试的人都知道它通常很脆弱。通过保持模型独立于视图,可以确保模型易于测试,而不依赖于 UI 构造。由于模型通常包含应用程序的业务逻辑,这显然是一件好事!

视图可以使用控制器传递的数据来生成适当的 HTML 响应。视图仅负责生成数据的最终表示;它不涉及任何业务逻辑。

这就是与 Web 应用程序相关的 MVC 设计模式的全部内容。许多与 MVC 相关的容易混淆的概念似乎源于对略有不同的框架和应用程序类型相关的术语略有不同。在下一节中,我将展示 ASP.NET Core 框架如何在 Razor Pages 中使用 MVC 模式,以及该模式的更多实例。

4.1.3 将 MVC 设计模式应用于 Razor Page

在上一节中,我讨论了 MVC 模式,因为它通常用于 Web 应用程序;Razor Pages 使用此模式。但是 ASP.NET Core 还包括一个名为 ASP.NET Core MVC 的框架。这个框架(毫不奇怪)非常接近 MVC 设计模式,使用控制器和 Action 方法代替 Razor Pages 和页面处理程序。其实,Razor Pages 是直接构建在底层 ASP.NET Core MVC 框架之上,使用底层 MVC 框架来实现其行为。

如果您愿意,可以完全避免 Razor Pages,直接在 ASP.NET Core 中使用 MVC 框架。这是早期版本 ASP.NET Core 和早期版本 ASP.NET 中的唯一选项。

提示:我在第4.2节中更深入地研究了 Razor Pages 和 MVC 框架之间的选择。

在本节中,我们将更深入地了解 MVC 设计模式如何应用于 ASP.NET Core 中的 Razor Pages。这也有助于理解 Razor Pages 的各种功能。

Razor Pages 使用 MVC 还是 MVVM?
我偶尔看到人们将 Razor Pages 描述为使用模型-视图-视图-模型(MVVM)设计模式,而不是 MVC 设计模式。就我个人而言,我不同意,但值得注意的是其中的差异。
MVVM 是一种 UI 模式,常用于移动应用程序、桌面应用程序和一些客户端框架。它与 MVC 的不同之处在于视图和视图模型之间存在双向交互。视图模型告诉视图要显示什么,但视图也可以直接在视图模型上触发更改。它通常用于双向数据绑定,其中视图模型“绑定”到视图。

正如您在前几章中所看到的,ASP.NET Core 使用 RoutingMiddleware 和 EndpointMiddleware 的组合实现 Razor Page 端点,如图 4.2 所示。一旦早期中间件处理了请求(假设没有一个中间件处理过请求并使管道短路),路由中间件将选择应该执行哪个 Razor Page handler,并且端点中间件执行页面处理程序。

 图4.2 典型 ASP.NET Core 应用程序的中间件管道。请求由每个中间件依次处理。如果请求到达路由中间件,中间件将选择一个端点(如 Razor Page)来执行。端点中间件执行所选端点。

中间件经常处理一些简单问题或狭义的请求,例如文件请求。对于超出这些功能或具有许多外部依赖性的需求,需要一个更健壮的框架。Razor Pages(和/或 ASP.NET Core MVC)可以提供此框架,允许与应用程序的核心业务逻辑交互并生成 UI。它处理从将请求映射到适当的控制器到生成 HTML 或 API 响应的所有事情。

在 MVC 设计模式的传统描述中,只有一种类型的模型,它包含所有非 UI 数据和行为。控制器根据需要更新该模型,然后将其传递给视图,视图使用该模型生成 UI。

讨论 MVC 时的一个问题是它使用的模糊和模棱两可的术语,例如“控制器”和“模型”。特别是模型,它是一个过载的术语,通常很难确定它到底指的是什么-它是对象、对象集合还是抽象概念?您很快就会看到,即使是 ASP.NET Core 也使用“模型”一词来描述几个相关但不同的组件。

将请求定向到 Razor Page 并构建绑定模型

应用程序收到请求的第一步是将请求路由到适当的 Razor Page 处理程序。让我们再次考虑类别待办事项列表页面,从列表 4.1 开始。在此页面上,您将显示具有给定类别标签的项目列表。如果您正在查看类别为“Simple”的项目列表,则会向 /categy/Simple 路径发出请求。

路由采用请求的标头和路径 /category/Simple,并将其映射到预先注册的模式列表。这些模式每个都匹配到单个 Razor Page 和页面处理程序的路径。您将在下一章中了解有关路由的更多信息。

提示:我使用术语Razor Page来指Razor视图和包含页面处理程序的PageModel的组合。注意,PageModel类不是我们在描述MVC模式时所指的“模型”。它扮演了其他角色,您将在本节稍后部分看到。

一旦选择了页面处理程序,就会生成绑定模型(如果适用)。该模型是基于传入请求、标记为绑定的 PageModel 的属性以及页面处理程序所需的方法参数构建的,如图 4.3 所示。绑定模型通常是一个或多个标准 C# 对象,其属性映射到请求的数据。我们将在第 6 章中详细讨论绑定模型。

 图4.3将请求路由到控制器并构建绑定模型。对 /categy/SimpleURL 的请求导致执行 CategoryModel.OnGet 页面处理程序,并传递填充的绑定模型 category。

定义:绑定模型是一个或多个对象,充当页面处理程序所需的请求数据中提供的数据的“容器”。

在本例中,绑定模型的是一个简单的字符串变量 category,它绑定到“simple”值。此值在请求 URL 的路径中提供。当然,也可以使用更复杂的绑定模型,其中填充了多个属性。

本例中的绑定模型对应于 OnGet 页面处理程序的方法参数。Razor Page 的实例是使用其构造函数创建的,绑定模型在执行时传递给页面处理程序,因此可以使用它来决定如何响应。对于本例,页面处理程序使用它来决定要在页面上显示哪些待办事项。

使用应用程序模型执行处理程序

页面处理程序作为 MVC 模式中的控制器的作用是协调生成对其处理的请求的响应。这意味着它只能执行有限数量的操作。特别是,它应该

  • 验证所提供的绑定模型中包含的数据对请求有效
  • 使用服务在应用程序模型上调用适当的操作
  • 根据应用程序模型的响应选择要生成的适当响应

图 4.4 显示了在应用程序模型上调用适当方法的页面处理程序。在这里,您可以看到应用程序模型是一个有点抽象的概念,它封装了应用程序的其余非 UI 部分。它包含域模型、许多服务和数据库交互。

 图 4.4 执行时,一个动作将调用应用程序模型中的适当方法。

定义:域模型将复杂的业务逻辑封装在一系列类中,这些类不依赖于任何基础设施,并且可以很容易地进行测试。

页面处理程序通常调用应用程序模型中的单个点。在我们查看待办事项列表类别的示例中,应用程序模型可能会使用各种服务来检查当前用户是否被允许查看某些项目、搜索给定类别中的项目、从数据库加载详细信息或从文件加载与项目相关联的图片。

假设请求有效,应用程序模型将向页面处理程序返回所需的详细信息。然后由页面处理程序选择要生成的响应。

使用视图模型构建 HTML

一旦页面处理程序调用了包含应用程序业务逻辑的应用程序模型,就应该生成响应了。视图模型捕获视图生成响应所需的详细信息。

定义:MVC 模式中的视图模型是视图呈现UI所需的所有数据。这通常是应用程序模型中包含的数据的一些转换,以及呈现页面所需的额外信息,例如页面标题。

术语视图模型在 ASP.NET Core MVC 中广泛使用,它通常指传递给 Razor 视图进行渲染的单个对象。然而,使用 Razor Pages,Razo r视图可以直接访问 Razor Page 的页面模型类。因此,Razor Page PageModel 通常充当 Razor Page 中的视图模型,Razor 视图所需的数据通过属性公开,如您之前在清单 4.1 中所见。

注意:Razor Pages 使用 PageModel 类本身作为 Razor 视图的视图模型,将所需数据作为属性公开。

Razor 视图使用页面模型中公开的数据来生成最终的 HTML 响应。最后,通过中间件管道将其发送回用户的浏览器,如图 4.5 所示。

 图4.5 页面处理程序通过在 PageModel 上设置属性来构建视图模型。生成响应的是视图。

需要注意的是,尽管页面处理程序选择是否执行视图和要使用的数据,但它并不控制生成的 HTML。是视图本身决定了响应的内容。

把这一切放在一起:一个完整的 Razor Page 请求

现在您已经看到了使用 Razor Pages 在 ASP.NET Core 中处理请求的每一个步骤,让我们将其从请求到响应放在一起。图 4.6 显示了如何组合这些步骤来处理显示“Simple”类别待办事项列表的请求。传统的 MVC 模式在 Razor Pages 中仍然可见,它由页面处理程序(控制器)、视图和应用程序模型组成。

  图4.6 “简单”类别中待办事项列表的完整 Razor Pages 请求

到目前为止,您可能会认为整个过程似乎相当复杂,需要这么多步骤才能显示一些 HTML!为什么不允许应用程序模型直接创建视图,而是必须使用页面处理程序方法来回跳舞?

整个过程的关键好处是将关注点分离:

  • 视图只负责获取一些数据并生成 HTML。
  • 应用程序模型只负责执行所需的业务逻辑。
  • 页面处理程序(控制器)仅负责验证传入的请求,并根据应用程序模型的输出选择所需的响应。

通过明确定义边界,可以更容易地更新和测试每个组件,而不依赖其他组件。如果您的UI逻辑发生了变化,您不必修改任何业务逻辑类,因此您不太可能在意外的地方引入错误。

紧密耦合的危险
一般来说,尽可能减少应用程序逻辑上分离的部分之间的耦合是一个好主意。这使得更新应用程序更容易,而不会造成不利影响或需要在看似无关的领域进行修改。应用 MVC 模式是帮助实现这一目标的一种方式。
作为一个例子,我记得几年前我在开发一个小型网络应用时的一个案例。在匆忙中,我们没有将我们的业务逻辑与 HTML 生成代码正确地分离,但最初代码没有明显的问题,所以我们将其交付!
几个月后,有人开始开发该应用程序,并立即“帮助”重命名了业务层某个类中的一个无害拼写错误。不幸的是,这些类的名称被用于生成 HTML 代码,因此重命名类会导致整个网站在用户的浏览器中崩溃!可以说,在那之后,我们做出了一致的努力来应用 MVC 模式,并确保我们有适当的关注点分离。

本章所示的示例演示了 Razor Pages 的大部分功能。它还有其他功能,比如过滤管道,我将在后面介绍(第13章),第 6 章将更深入地讨论绑定模型,但系统的整体行为是相同的。

同样,在第 9 章中,我将讨论当您使用 Web API 控制器生成机器可读响应时,MVC 设计模式是如何应用的。除产生的最终结果外,该过程在所有意图和目的上都是相同的。

在下一节中,您将看到如何将 Razor Pages 添加到应用程序中。默认情况下,Visual Studio 和 .NET CLI 中的一些模板将包含 Razor Pages,但您将看到如何将其添加到现有应用程序中并探索各种可用选项。

4.1.4 向应用程序添加 Razor Pages

MVC 基础设施,无论是 Razor Pages 还是 MVC/API 控制器,都是除了最简单的 ASP.NET Core 应用程序之外的所有应用程序的基础,因此几乎所有模板都包含默认配置的 MVC 基础设施。但为了确保您能够轻松地将 Razor Pages 添加到现有项目中,我将向您展示如何从一个基本的空应用程序开始,并从头开始将 Razor Pages 添加到此项目中。

你努力的结果还不会令人兴奋。我们仅在网页上显示“Hello World”,但这将显示将 ASP.NET Core 应用程序转换为使用 Razor Pages 是多么简单。它还将强调 ASP.NET Core 的可插拔性——如果您不需要 Razor Pages 提供的功能,完全可以不必使用它。

以下是如何将 Razor Pages 添加到应用程序中:

1. 在 Visual Studio 2019 中,选择“文件”>“新建”>“项目”,或从启动屏幕中选择“创建新项目”。
2. 从模板列表中,选择 ASP.NET Core Web 应用程序,确保选择 C# 语言模板。
3. 在下一个屏幕上,输入项目名称、位置和解决方案名称,然后单击创建。
4. 在下面的屏幕上,通过在 Visual Studio 中选择 ASP.NET Core 空项目模板,创建一个没有 MVC 或 Razor Pages 的基本模板,如图4.7所示。您可以使用 .NET CLI 和 dotnet new Web 命令创建一个类似的空项目。

图4.7 创建空 ASP.NET Core 模板。空模板将创建一个简单的 ASP.NET Core 应用程序,该应用程序包含一个没有 Razor Pages 的小型中间件管道。

5. 在 Startup.cs 文件的 ConfigureServices 方法中添加必要的 Razor Page 服务(以粗体显示):

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazor Pages();
}
6. 用 MapRazor Pages() 扩展方法(粗体)替换中间件管道末端 EndpointMiddleware 中配置的现有基本端点。为了简单起见,还可以从 Startup.cs 的 Configure 方法中删除现有的错误处理程序中间件:
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
  app.UseRouting();
  app.UseEndpoints(endpoints=>
  {
    endpoints.MapRazor Pages();
  });
}
7. 右键单击解决方案资源管理器中的项目,然后选择“添加”>“新建文件夹”,将新文件夹添加到项目的根目录中。将新文件夹命名为“Pages”。您现在已经将项目配置为使用 Razor Pages,但您还没有任何页面。以下步骤将新的 Razor Page 添加到应用程序中。可以使用 .NET CLI 创建类似的 Razor Page,通过从项目目录运行 dotnet new page-n Index -o Pages。
8. 右键单击新页面文件夹并选择 Add > Razor Page,如图4.8所示。

图4.8 向项目添加新的Razor页面

9. 在下一页中,选择R azor Page–Empty,然后单击 Add。在下面的对话框中,将页面命名为 Index.cshtml,如图 4.9 所示。

图4.9 使用AddRazor Page对话框创建新的Razor页面

10. Visual Studio 完成生成文件后,打开 Index.cshtml 文件,并更新 HTML 以表示 Hello World!通过将文件内容替换为以下内容:
@page
@model AddingRazor PagesToEmptyProject.IndexModel
@{
  Layout = null;
}
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>

完成所有这些步骤后,您应该能够恢复、构建和运行应用程序。在 Visual Studio 2019 中,选择“文件”>“新建”>“项目”,或从启动屏幕中选择“创建新项目”。

注意:您可以在 Visual Studio 中按 F5 键(或在项目文件夹的命令行中调用 dotnet run)来运行项目。这将恢复所有引用的 NuGet 包,构建项目,并启动应用程序。Visual Studio 将自动打开浏览器窗口以访问应用程序的主页。

当您向根“/”路径发出请求时,应用程序会调用 IndexModel 上的 OnGet 处理程序,这是由于 Razor Pages 基于文件名的常规路由方式。现在不要担心这一点;我们将在下一章详细讨论。

OnGet 处理程序是一个 void 方法,它使 Razor Page 呈现关联的 Razor 视图,并在响应中将其发送到用户的浏览器。

Razor Pages 依赖于许多内部服务来执行其功能,这些服务必须在应用程序启动期间注册。这是通过调用 Startup.cs 的 ConfigureServices 方法中的 AddRazor Pages 实现的。如果没有这一点,你的应用程序启动时会出现异常,提醒你需要调用。

Configure 中对 MapRazor Pages 的调用将 Razor Page 端点注册到端点中间件。作为此调用的一部分,将自动注册用于将URL路径映射到特定 Razor Page 处理程序的路由。

注:我将在下一章详细介绍路由。

本节中的说明描述了如何将Razor Pages添加到应用程序中,但这并不是将HTML生成添加到应用中的唯一方法。正如我之前提到的,Razor Pages 构建在 ASP.NET Core MVC 框架之上,并共享许多相同的概念。在下一节中,我们将简要介绍 MVC 控制器,看看它们 与Razor Pages 的比较,并讨论何时应该选择使用一种方法而不是另一种方法。

4.2 Razor Pages 与 ASP.NET Core 中的 MVC

在本书中,我将重点放在 Razor Pages 上,因为这已经成为使用 ASP.NET Core 构建服务器端渲染应用程序的推荐方法。然而,我还提到 Razor Pages 在幕后使用 ASP.NET Core MVC 框架,如果您愿意,可以选择直接使用它。此外,如果您正在创建一个用于处理移动或客户端应用程序的 Web API,那么您几乎可以直接使用 MVC 框架。

注:我在第 9 章中介绍了如何构建 Web API。

那么 Razor Pages 和 MVC 之间有什么区别,您应该在什么时候选择一个或另一个呢?

如果您是 ASP.NET Core 的新手,那么答案很简单:使用 Razor Pages 来构建服务器端呈现的应用程序,使用 MVC 框架来构建 WebAPI。我将在后面的章节中讨论一些细微差别,但这种区别最初会很好地为您服务。

如果您熟悉 ASP.NET 的早期版本或 ASP.NET Core 的早期版本,并且正在决定是否使用 Razor Pages,那么本节将帮助您进行选择。来自这些背景的开发人员最初常常对 Razor Pages 有误解(正如我所做的!),错误地将其等同于 WebForm,忽视了 MVC 框架的底层基础。

但是,在我们进行比较之前,我们应该简要了解一下 ASP.NET Core MVC 框架本身。了解 MVC 和 Razor Pages 之间的异同非常有用,因为您可能会在某个时候发现 MVC 的用处,即使您大部分时间都使用 Razor Page。

4.2.1 ASP.NET Core 中的 MVC 控制器

在第4.1节中,我们研究了 MVC 设计模式,以及它如何应用于 ASP.NET Core 中的 Razor Pages。也许毫不奇怪,您可以以几乎完全相同的方式使用 ASP.NET Core MVC 框架。为了演示 Razor Pages 和 MVC 之间的区别,我们将查看清单 4.1 中 Razor Page 的 MVC 版本,其中显示了给定类别的待办事项列表。

MVC 使用了控制器和操作方法的概念,而不是 PageModel 和页面处理程序。如图 4.10 所示,它们几乎直接类似于 Razor Pages 的副本,图 4.10 显示了与图 4.6 等效的 MVC。另一方面,MVC 控制器使用显式视图模型将数据传递给 Razor 视图,而不是将数据作为属性本身公开(正如 Razor page 对页面模型所做的那样)。

定义:动作(或动作方法)是响应请求而运行的方法。MVC 控制器是一个包含多个逻辑分组的动作方法的类。

图4.10 完整 MVC 控制器请求。MVC 控制器模式与 Razor Pages 模式几乎相同,如图 4.6 所示。控制器相当于 Razor Page,操作相当于页面处理程序。

清单 4.2 显示了一个 MVC 控制器的示例,它提供了与清单 4.1 中的 Razor Page 相同的功能。在 MVC 中,控制器通常用于将类似的动作聚合在一起,因此这种情况下的控制器称为 ToDoController,因为它通常包含用于处理待办事项的其他动作方法,例如查看特定项目或创建新项目的动作。

清单 4.2 用于查看给定类别中所有待办事项的 MVC 控制器

public class ToDoController : Controller
{
    private readonly ToDoService _service; 
    public ToDoController(ToDoService service)    //ToDoService是使用依赖注入在控制器构造函数中提供的。
    {
        _service = service;
    }
    public ActionResult Category(string id)    //Category操作方法接受一个参数id。
    {
        var items = _service.GetItemsForCategory(id);    //action方法调用ToDoService以检索数据并构建视图模型。
        var viewModel = new CategoryViewModel(items);    //视图模型是一个简单的C#类,在应用程序的其他地方定义。
        return View(viewModel);    //返回一个ViewResult,指示应渲染Razor视图,并传入视图模型
    }
    //MVC控制器通常包含响应不同请求的多个动作方法。 
    public ActionResult Create(ToDoListModel model)
    {
        // ...
    }
}

除了一些命名上的差异,ToDoController 看起来与清单 4.1 中的 Razor Page 非常相似。实际上,从架构上讲,Razor Pages 和 MVC 本质上是等效的,因为它们都使用 MVC 设计模式。最明显的差异与文件在项目中的位置有关,我将在下一节中讨论。

4.2.2 Razor Pages 的优势

在上一节中,我展示了 MVC 控制器的代码看起来与 Razor PageModel 的代码非常相似。如果是这样的话,使用 Razor Pages 有什么好处?在本节中,我将讨论 MVC 控制器的一些痛点,以及 Razor Pages 如何解决这些痛点。

Razor 页面不是 WebForm

我从现有 ASP.NET 开发人员那里听到的反对 Razor Pages 的一个常见论点是“哦,他们只是 WebForm。”这种观点在许多不同的方面都没有抓住重点,但这是很常见的,值得直接解决。

Web 窗体是一种 Web 编程模型,于 2002 年作为 .NET Framework 1.0 的一部分发布。它们试图为首次从桌面开发转向 Web 的开发人员提供高效的体验。

Web 表单现在受到了很多诋毁,但它们的弱点后来才变得明显。Web 表单试图将网络的复杂性隐藏起来,让您感觉使用桌面应用程序开发。这通常会导致应用程序速度慢,相互依赖性强,难以维护。

Web 表单提供了基于页面的编程模型,这就是为什么 Razor Pages 有时会与之关联。然而,正如您所看到的,Razor Pages 是基于 MVC 设计模式的,它暴露了 Web 的内在特性,而不试图隐藏它们。

Razor Pages 使用约定(您已经看到了其中的一些约定)优化了某些流,但它并不像 WebForms 那样试图在无状态 Web 应用程序的顶部构建有状态的应用程序模型。

在 MVC 中,一个控制器可以有多个动作方法。每个操作处理不同的请求并生成不同的响应。控制器中多个动作的分组有些随意,但通常用于对与特定实体相关的动作进行分组:在本例中为待办事项列表项。例如,清单 4.2 中的 ToDoController 的更完整版本可能包括列出所有待办事项、创建新项目和删除项目的操作方法。不幸的是,您经常会发现您的控制器变得非常庞大和臃肿,具有许多依赖关系。

注:您不必像这样使控制器非常大。这只是一种常见的模式。例如,您可以为每个操作创建单独的控制器。

MVC 控制器的另一个缺陷是它们在项目中的组织方式。控制器中的大多数操作方法都需要关联的 Razor 视图,以及用于向视图传递数据的视图模型。MVC 方法传统上按类型(控制器、视图、视图模型)对类进行分组,而 Razor Page 方法按功能对与特定页面相关的所有内容进行分组。

图 4.11 将简单 Razor Pages 项目的文件布局与 MVC 等效项目进行了比较。使用 Razor Pages 意味着无论何时在处理特定页面时,都可以在控制器、视图和视图模型文件夹之间上下滚动。您所需的一切都在两个文件中找到,.cshtml Razo r视图和 .cshtml.cs PageModel 文件。

图4.11 比较 MVC 项目的文件夹结构与 Razor Pages 项目的文件夹架构

MVC 和 Razor Pages 之间还有其他不同之处,我将在本书中强调,但这种布局差异确实是很大的益处。Razor Pages 接受这样一个事实,即您正在构建基于页面的应用程序,并通过将与单个页面相关的所有内容放在一起来优化您的工作流程。

提示:您可以将每个 Razor Page 视为专注于单个页面的迷你控制器。页面处理程序在功能上等同于 MVC 控制器操作方法。

这种布局还具有使每个页面成为单独的类的优点。这与 MVC 方法形成了对比,MVC 方法将每个页面作为给定控制器上的操作。每个 Razor Page 都是针对特定功能的,例如显示待办事项。MVC 控制器包含处理多个不同功能的动作方法,以实现更抽象的概念,例如与待办事项相关的所有功能。

另一个重要的点是 Razor Pages 并没有失去 MVC 所具有的关注点分离。Razor Pages 的视图部分仍然只关注呈现 HTML,处理程序是调用应用程序模型的协调器。唯一真正的区别是缺少 MVC 中的显式视图模型,但如果这对您来说是一个破坏,那么在 Razor Pages 中模仿它是完全可能的。

使用 Razor Pages 的好处在您拥有“内容”网站时尤其明显,例如营销网站,在那里您主要显示静态数据,而且没有真正的逻辑。在这种情况下,MVC 增加了复杂性,但没有任何实际好处,因为控制器中根本没有任何逻辑。另一个很好的用例是创建表单供用户提交数据。Razor Pages 特别针对这个场景进行了优化,您将在后面的章节中看到。

显然,我是 Razor Pages 的粉丝,但这并不是说它们适合任何情况。在下一节中,我将讨论在应用程序中选择使用 MVC 控制器的一些情况。记住,这不是非此即彼的选择,在同一应用程序中同时使用 MVC 控制器和 Razor Pages 是可能的,在很多情况下,这可能是最好的选择。

4.2.3 何时选择 MVC 控制器而不是 Razor Pages

Razor Pages 非常适合构建基于页面的服务器端渲染应用程序。但并非所有的应用程序都符合这种模式,甚至有些属于这种类型的应用程序可能最好使用 MVC 控制器而不是 Razor Pages 来开发。以下是一些这样的场景:

  • 当您不想呈现视图时,Razor Pages 最适合基于页面的应用程序,在那里您为用户呈现视图。如果您正在构建 Web API,则应该使用 MVC 控制器。
  • 当您将现有 MVC 应用程序转换为 ASP.NET Core 时如果您已经有一个使用 MVC 的 ASP.NET 应用程序,那么将现有 MVC 控制器转换为 Razor Pages 可能不值得。保留现有代码更有意义,也许可以考虑使用 Razor Pages 在应用程序中进行新的开发。
  • 当您进行大量部分页面更新时,可以在应用程序中使用 JavaScript,通过一次仅更新部分页面来避免进行全页面导航。这种方法介于完全服务器端渲染和客户端应用程序之间,使用MVC控制器可能比Razor Pages更容易实现。

何时不使用 Razor Pages 或 MVC 控制器
通常,您将使用 Razor Pages 或 MVC 控制器为应用程序编写大部分应用程序逻辑。您将使用它来定义应用程序中的 API 和页面,并定义它们如何与业务逻辑交互。Razor Pages 和 MVC 提供了一个广泛的框架(您将在接下来的六章中看到),它提供了大量功能,帮助快速高效地构建应用程序。但它们并不适合每个应用。

提供如此多的功能必然会带来一定程度的性能开销。对于典型的应用程序,使用 MVC 或 Razor Pages 带来的生产力收益远远超过了性能影响。但是,如果您正在为云构建小型、轻量级的应用程序,您可以考虑直接使用定制中间件(参见第19章)或 gRPC 等替代协议(https://docs.microsoft.com/ASP.NET/core/grpc)。你可能还想看看 Christian Horsdal Gammelgaard(Manning,2017)的《Microservices in .NET Core》。

或者,如果您正在构建具有实时功能的应用程序,您可能会考虑使用 WebSocket 而不是传统的 HTTP 请求。ASP.NET Core SignalR 可以通过在 WebSocket 上提供抽象来为应用程序添加实时功能。SignalR 还提供了简单的传输回退和远程过程调用(RPC)应用程序模型。有关详细信息,请参阅文档https://docs.microsoft.com/ASP.NET/core/signalr

ASP.NET Core 5.0 中的另一个选项是 Blazor。此框架允许您通过利用 WebAssembly 标准直接在浏览器中运行 .NET 代码,或者使用 SignalR 的有状态模型来构建交互式客户端 We b应用程序。有关详细信息,请参阅文档,网址:https://docs.microsoft.com/ASP.NET/core/blazor/

希望此时您已了解 Razor Pages 及其整体设计。到目前为止,我们看过的所有 Razor Pages 都使用了一个页面处理程序。在下一节中,我们将更深入地研究页面处理程序:如何定义它们,如何调用它们,以及如何使用它们来呈现 Razor 视图。

4.3 Razor Pages 和页面处理程序

在本章的第一节中,我描述了 MVC 设计模式及其与 ASP.NET Core 的关系。在设计模式中,控制器接收请求,并且是 UI 生成的入口点。对于 Razor Pages,入口点是驻留在 Razor Page 的 PageModel 中的页面处理程序。页面处理程序是响应请求而运行的方法。

默认情况下,磁盘上 Razor Page 的路径控制 Razor 页响应的 URL 路径。例如,对 URL/products/list 的请求对应于路径 pages/products/list.cshtml 上的 Razor 页面。Razor Pages 可以包含任意数量的页面处理程序,但只有一个页面处理程序响应给定的请求运行。

注意:在下一章中,您将了解有关选择 Razor Page 和处理程序(称为路由)的更多信息。

页面处理程序的职责通常有三重:

  • 确认传入请求有效。
  • 调用与传入请求相对应的适当业务逻辑。
  • 选择要返回的适当类型的响应。

页面处理程序不需要执行所有这些操作,但至少它必须选择要返回的响应类型。页面处理程序通常返回以下三项之一:

  • PageResult 对象 —— 这将导致关联的 Razor 视图生成 HTML 响应。
  • Nothing(处理程序返回 void 或 Task)—— 这与前面的情况相同,导致 Razor 视图生成 HTML 响应。
  • RedirectToPageResult —— 这表示应该将用户重定向到应用程序中的其他页面。

这些是 Razor Pages 最常用的结果,但我在第 4.3.2 节中描述了一些其他选项。

重要的是要认识到页面处理程序不会直接生成响应;它选择响应类型并为其准备数据。例如,返回P ageResult 时不会生成任何 HTML;它仅仅指示应当呈现视图。这与 MVC 设计模式保持一致,其中生成响应的是视图,而不是控制器。

提示:页面处理程序负责选择要发送的响应类型;MVC 框架中的视图引擎使用结果来生成响应。

还值得记住的是,页面处理程序通常不应该直接执行业务逻辑。相反,他们应该在应用程序模型中调用适当的服务来处理请求。例如,如果页面处理程序收到将产品添加到用户购物车的请求,则不应直接操作数据库或重新计算购物车总数。相反,它应该调用另一个类来处理细节。这种分离关注点的方法确保了代码在增长时保持可测试性和可维护性。

4.3.1 接受页面处理程序的参数

向页面处理程序发出的某些请求将需要附加值以及有关请求的详细信息。如果请求是搜索页面,则请求可能包含搜索词的详细信息和他们正在查看的页码。如果请求向应用程序发布表单,例如用户使用用户名和密码登录,则这些值必须包含在请求中。在其他情况下,将没有值,例如当用户请求应用程序的主页时。

请求可能包含来自各种不同来源的附加值。它们可以是 URL、查询字符串、标头或请求本身的一部分。中间件将从每个源中提取值,并将它们转换为 .NET 类型。

定义:从请求中提取值并将其转换为 .NET 类型的过程称为模型绑定。我在第 6 章中讨论了模型绑定。

ASP.NET Core 可以在 Razor Pages 中绑定两个不同的目标:

  • 方法参数 —— 如果页面处理程序具有方法参数,则使用请求中的值来创建所需的参数。
  • 用 [BindProperty] 属性标记的属性——将绑定用该属性标记的任何属性。默认情况下,此属性对 GET 请求不起作用。

模型绑定值可以是简单类型,例如字符串和整数,也可以是复杂类型,如以下列表所示。如果请求中提供的任何值未绑定到属性或页面处理程序参数,则其他值将不使用。

清单4.3 Razor 页面处理程序示例

public class SearchModel : PageModel
{
    private readonly SearchService _searchService; 
    //SearchService提供给SearchModel以用于页面处理程序。
    public SearchModel(SearchService searchService)
    {
        _searchService = searchService;
    }
    
    [BindProperty]
    public BindingModel Input { get; set; }     //用[BindProperty]属性修饰的属性将被模型绑定。
    public List<Product> Results { get; set; }    //未修饰的属性将不会绑定到模型。
    
    //页面处理程序不需要检查模型是否有效。返回void将渲染视图。
    public void OnGet()
    {
    }
    
    public IActionResult OnPost(int max)    //此页面处理程序中的max参数将使用请求中的值进行模型绑定。
    {
        //如果请求无效,则该方法指示应将用户重定向到索引页。 
        if (ModelState.IsValid)
        {
            Results = _searchService.Search (Input.SearchTerm, max); 
            return Page();
        }
        return RedirectToPage("./Index");
    }
}

在本例中,OnGet 处理程序不需要任何参数,而且方法很简单,它返回 void,这意味着将呈现关联的 Razor 视图。它还可能返回 PageResult;效果也会是一样的。请注意,此处理程序用于 HTTP GET 请求,因此未绑定用 [BindProperty] 修饰的 Input 属性。

提示:要绑定 GET 请求的属性,请使用属性的 SupportsGet 属性;例如 [BindProperty(SupportsGet=true)]。

相反,OnPost 处理程序接受参数 max 作为参数。在本例中,它是一个简单的类型 int,但也可以是一个复杂的对象。此外,由于此处理程序对应于 HTTP POST 请求,因此 Input 属性也被模型绑定到请求。

注意:与大多数 .NET 类不同,您不能使用方法重载在 Razor Page 上使用相同名称的多个页面处理程序。

当操作方法使用模型绑定属性或参数时,应始终使用 ModelState.IsValid 检查所提供的模型是否有效。ModelState 属性作为基 PageModel 类上的属性公开,可用于检查所有绑定的属性和参数是否有效。当您了解验证时,您将在第 6 章中看到该过程是如何工作的。

一旦页面处理程序确定提供给操作的方法参数有效,它就可以执行适当的业务逻辑并处理请求。对于 OnPost 处理程序,这涉及调用提供的 SearchService 并在 Results 属性上设置结果。最后,处理程序通过调用基方法返回 PageResult

return Page();

如果模型无效,则没有任何结果可显示!在此示例中,该操作使用 RedirectToPage 帮助器方法返回 RedirectToPageResult。执行时,此结果将向用户发送 302 重定向响应,这将导致其浏览器导航到 Index Razor Page。

注意,OnGet 方法在方法签名中返回 void,而 OnPost 方法返回 IActionResult。这在 OnPost 方法中是必需的,以便允许 C# 编译(因为 Page 和 RedirectToPage 帮助器方法返回不同的类型),但它不会改变方法的最终行为。您可以很容易地在 OnGet 方法中调用 Page 并返回一个 IActionResult,其行为将是相同的。

提示:如果要从页面处理程序返回多个类型的结果,则需要确保方法返回 IActionResult。

在下一节中,我们将更深入地了解操作结果及其用途。

4.3.2 返回带有 ActionResults 的响应

在上一节中,我强调了页面处理程序决定返回什么类型的响应,但它们自己不会生成响应。它是页面处理程序返回的 IActionResult,当 Razor Pages 基础结构使用视图引擎执行该处理程序时,将生成响应。

这种方法是遵循 MVC 设计模式的关键。它将发送何种响应的决定与响应的生成分开。这允许您轻松测试动作方法逻辑,以确认为给定输入发送了正确类型的响应。例如,您可以单独测试给定的 IActionResult 是否生成预期的 HTML。

ASP.NET Core有 许多不同类型的 IActionResult:

  • PageResult —— 为 Razor Pages 中的关联页面生成 HTML 视图
  • ViewResult —— 使用 MVC 控制器时为给定 Razor 视图生成 HTML 视图
  • RedirectToPageResult —— 发送 302 HTTP 重定向响应以自动将用户发送到另一个页面
  • RedirectResult —— 发送 302 HTTP 重定向响应,自动将用户发送到指定的 URL(不必是 Razor Page)
  • FileResult —— 返回文件作为响应
  • ContentResult —— 返回提供的字符串作为响应
  • StatusCodeResult —— 发送原始 HTTP 状态代码作为响应,可以选择与相关的响应正文内容
  • NotFoundResult —— 发送原始 404 HTTP 状态代码作为响应

当 Razor Pages 执行这些命令时,将生成一个响应,通过中间件管道发送回用户。

提示:当您使用 Razor Pages 时,通常不会使用其中的一些操作结果,例如 ContentResult和StatusCodeResult。不过,了解它们是很好的,因为如果您使用 MVC 控制器构建 Web API,您可能会使用它们。

在本节中,我将简要介绍 Razor Pages 中使用的最常见的 IActionResult类。

PageResult 和 RedirectToPageResult

当您使用 Razor Pages 构建传统的 Web 应用程序时,通常会使用 PageResult,后者使用 Razor 生成 HTML 响应。我们将在第 7 章中详细了解这是如何发生的。

您还通常使用各种基于重定向的结果将用户发送到新的网页。例如,当您在电子商务网站上下单时,通常会浏览多个页面,如图 4.12 所示。每当需要您移动到其他页面时,例如当用户提交表单时,Web 应用程序就会发送 HTTP 重定向。您的浏览器会自动跟踪重定向请求,从而在结账过程中创建无缝流程。

图 4.12 通过网站的典型 POST、REDIRECT 和 GET 流程。用户将购物篮发送到结账页面,该页面验证其内容并重定向到付款页面,而无需用户手动更改 URL。

在这个流程中,每当您返回 HTML 时,都会使用 PageResult;重定向到新页面时,使用 RedirectToPageResult。

提示:Razor Pages 通常被设计为无状态的,因此如果您想在多个页面之间持久化数据,则需要将其放置在数据库或类似的存储中。如果您只想为单个请求存储数据,您可以使用 TempData,它为单个请求在 cookie 中存储少量数据。有关详细信息,请参阅文档:http://mng.bz/XdXp

未找到结果和状态搜索结果

除了 HTML 和重定向响应之外,您有时还需要发送特定的 HTTP 状态代码。如果您请求在电子商务应用程序上查看产品的页面,而该产品不存在,则会向浏览器返回 404 HTTP 状态代码,您通常会看到“未找到”网页。Razor Pages 可以通过返回 NotFoundResult 来实现此行为,该结果将返回原始的 404 HTTP 状态代码。使用 StatusCodeResult 并将显式返回的状态代码设置为 404,可以获得类似的结果。

注意:NotFoundResult 不会生成任何 HTML;它只生成一个原始的 404 状态代码,并通过中间件管道返回它。但是,如前一章所述,您可以使用 StatusCodePagesMiddleware 在生成原始 404 状态代码后拦截该代码,并为其提供用户友好的 HTML 响应。

使用助手方法创建 ActionResult 类

可以使用 C# 的普通新语法创建和返回 ActionResult 类:

return new PageResult()

然而,Razor PagesPageModel 基类还提供了许多用于生成响应的帮助器方法。通常使用 Page 方法生成适当的 PageResult,使用 RedirectToPage 方法生成 RedirectToPageResult,或使用 NotFound 方法生成 NotFoundResult。

提示:大多数 ActionResult 类在基本 PageModel 类上都有一个 helper 方法。它们通常命名为 Type,生成的结果称为 Type-result。例如,StatusCode 方法返回 StatusCodeResult 实例。

如前所述,返回 IActionResult 的行为不会立即生成响应,而是 Razor Pages 基础结构执行 IActionResult,这发生在 action 方法之外。生成响应后,Razor Pages 将其返回到中间件管道。从那里,它通过管道中所有注册的中间件,然后 ASP.NET Core Web 服务器最终将其发送给用户。

现在,您应该全面了解 MVC 设计模式,以及它与 ASP.NET Core 和 Razor Pages 的关系。Razor Page 上的页面处理程序方法是响应给定请求而调用的,用于通过返回 IActionResult 来选择要生成的响应类型。

重要的是要记住,ASP.NET Core 中的 MVC 和 Razor Pages 基础架构作为 EndpointMiddleware 管道的一部分运行,正如您在上一章中所看到的。生成的任何响应,无论是 PageResult 还是 RedirectToPageResult,都将通过中间件管道传回,从而为中间件提供了一个潜在的机会,以便在 Web 服务器将响应发送给用户之前观察该响应。

我只模糊地谈到了 RoutingMiddleware 如何决定为给定请求调用哪个 Razor Page 和处理程序。你不希望应用程序中的每个 URL 都有 Razor 页面。例如,在电子商店中,每个产品都很难有不同的页面——每个产品都需要自己的 Razor 页面!处理这个和其他场景是路由基础设施的作用,也是 ASP.NET Core 的关键部分。在下一章中,您将看到如何定义路由,如何向路由添加约束,以及如何解构 URL 以匹配单个 Razor Page 处理程序。

总结

  • MVC 设计模式允许在应用程序的业务逻辑、传递的数据和响应中的数据显示之间分离关注点。
  • Razor Pages是基于 ASP.NET Core MVC 框架构建的,它们使用许多相同的原语。他们使用约定和不同的项目布局来优化基于页面的场景。
  • MVC 控制器包含多个动作方法,通常围绕一个高级实体分组。Razor Pages 将单个页面的所有页面处理程序分组在一个位置,围绕页面/功能而不是实体分组。
  • 每个 Razor Page 相当于一个专注于单个页面的小型控制器,每个 Razor Page 处理程序对应于一个单独的操作方法。
  • Razor Pages 应该继承自 PageModel 基类。
  • 在一个称为路由的过程中,根据传入请求的 URL、HTTP 谓词和请求的查询字符串选择一个 Razor Page 处理程序。
  • 页面处理程序通常应该委托给服务来处理请求所需的业务逻辑,而不是自己执行更改。这确保了清晰的关注点分离,有助于测试并改进应用程序结构。
  • 页面处理程序可以具有参数,这些参数的值取自称为模型绑定的进程中传入请求的属性。用 [BindProperty] 修饰的属性也可以绑定到请求。
  • 默认情况下,用 [BindProperty] 修饰的属性不绑定 GET 请求。要启用绑定,请使用 [BindProperty(SupportsGet=true)]。
  • 页面处理程序可以返回 PageResult 或 void 以生成 HTML 响应。
  • 您可以使用 RedirectToPageResult 将用户发送到新的 Razor 页面。
  • PageModel 基类公开了许多用于创建 ActionResult 的帮助器方法。

如需导航到本书的其他章节,请点击这里...