Loading

ASP.NET Core 实战-4.使用 Razor 页面创建网站

在 ASP.NET Core Web 应用程序中,您的中间件管道通常包含 EndpointMiddleware。 这通常是您编写大部分应用程序逻辑的地方,在您的应用程序中调用各种其他类。 它还充当用户与您的应用程序交互的主要入口点。 它通常采用以下三种形式之一:

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

Razor Pages 简介

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

定义 基于页面的网站是用户在多个页面之间浏览,将数据输入表单,并通常消费内容的网站。 这与游戏或单页应用程序 (SPA) 等应用程序形成鲜明对比,后者在客户端进行大量交互。

探索典型的 Razor 页面

在第 1 章中,我们看到了一个非常简单的 Razor Page。 它不包含任何逻辑,而只是渲染了关联的 Razor 视图。例如,如果您正在构建一个内容密集的营销网站,这种模式可能很常见,但更常见的是,您的 Razor 页面将包含一些逻辑、从数据库加载数据或使用表单来允许用户提交信息。

为了让您更多地了解典型的 Razor 页面是如何工作的,在本节中,我们将简要介绍一个稍微复杂的 Razor 页面。 此页面取自待办事项列表应用程序,用于显示给定类别的所有待办事项。 此时我们不关注 HTML 生成,因此以下清单仅显示了 Razor 页面的 PageModel 代码隐藏。

清单 4.1 用于查看给定类别中所有待办事项的 Razor 页面

public class CategoryModel : PageModel
{
    private readonly ToDoService _service; //ToDoService 使用依赖注入在模型构造函数中提供。
    public CategoryModel(ToDoService service)
    {
        _service = service;
    }
    public ActionResult OnGet(string category) //OnGet 处理程序采用参数类别。
    {
        //处理程序调用 ToDoService 以检索数据并设置 Items 属性。
        Items = _service.GetItemsForCategory(category); 
        //返回一个 PageResult 指示应该呈现 Razor 视图
        return Page();
    }
    //Razor 视图可以在呈现时访问 Items 属性。
    public List<ToDoListModel> Items { get; set; }
}

这个例子仍然比较简单,但与第1章的基本例子相比,它展示了多种特性:

  • 页面处理程序 OnGet 接受方法参数 category。 此参数由 Razor 页面基础结构使用来自传入请求的值在称为模型绑定的过程中自动填充。
  • 处理程序不直接与数据库交互。 相反,它使用提供的类别值与 ToDoService 交互,使用依赖注入将其作为构造函数参数注入。
  • 处理程序在方法结束时返回 Page() 以指示应呈现关联的 Razor 视图。 在这种情况下,return 语句实际上是可选的; 按照惯例,如果页面处理程序是 void 方法,则仍将呈现 Razor 视图,表现得好像您在方法结束时调用了 return Page()。
  • Razor 视图可以访问 CategoryModel 实例,因此它可以访问由处理程序设置的 Items 属性。 它使用这些项目来构建最终发送给用户的 HTML。

清单 4.1 的 Razor 页面中的交互模式显示了一种常见模式。页面处理程序是 Razor 页面的中央控制器。 它接收来自用户的输入(类别方法参数),调用应用程序的“大脑”(ToDoService)并将数据(通过公开 Items 属性)传递给 Razor 视图,该视图生成 HTML 响应。 如果你眯着眼睛,这看起来像是模型-视图-控制器 (MVC) 设计模式。

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

MVC 设计模式

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

在深入探讨设计模式本身之前,让我们考虑一个典型的请求。 想象一下,您的应用程序的用户请求上一节中显示待办事项列表类别的 Razor 页面。 图 4.1 显示了 Razor 页面如何处理请求的不同方面,所有这些结合起来生成最终响应。

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

一般来说,三个“组件”构成了 MVC 设计模式:

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

MVC 设计模式中的每个组件都负责整个系统的一个方面,当它们组合在一起时,可用于生成 UI。 待办事项列表示例根据使用 Razor 页面的 Web 应用程序来考虑 MVC,但请求也可以等同于单击桌面 GUI 应用程序中的按钮。

一般来说,应用程序响应用户交互或请求时的事件顺序如下:

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

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

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

考虑查看电子商务应用程序的产品页面的请求。 控制器将收到请求,并知道如何联系作为应用程序模型一部分的某些产品服务。 这可能会从数据库中获取所请求产品的详细信息并将它们返回给控制器。

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

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

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

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

将 MVC 设计模式应用于 Razor 页面

ASP.NET Core 使用 RoutingMiddlewareEndpointMiddleware 的组合来实现 Razor Page 端点,如图 4.2 所示。 一旦较早的中间件处理了请求(并假设它们都没有处理请求并短路管道),路由中间件将选择应该执行哪个 Razor 页面处理程序,并且端点中间件执行页面处理程序。

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

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

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

讨论 MVC 时的问题之一是它使用的含糊不清的术语,例如“Controller”和“Model”。 尤其是模型,它是一个过度使用的术语,以至于通常很难确定它到底指的是什么——它是一个对象、一个对象的集合还是一个抽象概念? 甚至 ASP.NET Core 也使用“模型”一词来描述几个相关但不同的组件,您很快就会看到。

将请求定向到剃刀页面并构建绑定模型

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

路由获取请求的标头和路径 /category/Simple,并将其映射到预先注册的模式列表。 这些模式均匹配单个 Razor 页面和页面处理程序的路径。

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

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

在这种情况下,绑定模型是一个简单的字符串 category,它绑定到“Simple”值。 该值在请求 URL 的路径中提供。 也可以使用更复杂的绑定模型,其中填充了多个属性。

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

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

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

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

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

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

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

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

使用视图模型构建 HTML

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

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

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

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

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

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

将所有内容放在一起:完整的剃须刀页面请求

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

到现在为止,您可能会认为整个过程似乎相当复杂——显示一些 HTML 的步骤如此之多! 为什么不允许应用程序模型直接创建视图,而不必与页面处理程序方法来回跳舞?

整个过程的主要好处是关注点分离:

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

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

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

将 Razor 页面添加到您的应用程序

MVC 基础结构,无论是由 Razor 页面还是 MVC/API 控制器使用,都是除最简单的 ASP.NET Core 应用程序之外的所有应用程序的基础方面,因此几乎所有模板都包含它以某种方式默认配置。 但为了确保您对将 Razor Pages 添加到现有项目感到满意,我将向您展示如何开始 一个基本的空应用程序,然后从头开始向其中添加 Razor Pages。

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

以下是将 Razor Pages 添加到应用程序的方法:

在 Visual Studio 2019 中,选择“文件”>“新建”>“项目”或从初始屏幕中选择“创建新项目”。

从模板列表中,选择 ASP.NET Core Web 应用程序,确保选择 C# 语言模板。

在下一个屏幕上,输入项目名称、位置和解决方案名称,然后单击创建。

在以下屏幕上,通过在 Visual Studio 中选择 ASP.NET Core Empty 项目模板,创建一个没有 MVC 或 Razor Pages 的基本模板,如图 4.7 所示。 您可以使用 .NET CLI 和 dotnet new web 命令创建一个类似的空项目。

image

Startup.cs 文件的 ConfigureServices 方法中添加必要的 Razor Page 服务:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
}

将中间件管道末尾的 EndpointMiddleware 中配置的现有基本端点替换为 MapRazorPages() 扩展方法(粗体)。 为简单起见,现在还从 Startup.csConfigure 方法中删除现有的错误处理程序中间件:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRouting();
    app.UseEndpoints(endpoints =>
                     {
                         endpoints.MapRazorPages();
                     });
}

在解决方案资源管理器中右键单击您的项目,然后选择添加 > 新文件夹以将新文件夹添加到项目的根目录。 将新文件夹命名为“Pages”。

您现在已将项目配置为使用 Razor 页面,但您还没有任何页面。 以下步骤将新的 Razor 页面添加到您的应用程序。 您可以通过从项目目录运行 dotnet new page -n Index -o Pages/ 使用 .NET CLI 创建类似的 Razor 页面。

右键单击新建的 pages 文件夹,选择 Add > Razor Page,如图 4.8 所示。

image

在接下来的页面上,选择 Razor Page – Empty,然后单击添加。 在下面的对话框中,将您的页面命名为 Index.cshtml,如图 4.9 所示。

image

Visual Studio 完成文件生成后,打开 Index.cshtml 文件,并更新 HTML 以说 Hello World! 通过将文件内容替换为以下内容:

@page
@model AddingRazorPagesToEmptyProject.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>

完成所有这些步骤后,您应该能够恢复、构建和运行您的应用程序。

当您向根“/”路径发出请求时,应用程序将调用 IndexModel 上的 OnGet 处理程序,因为基于文件名的 Razor 页面的路由工作的传统方式。

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

Razor 页面依赖于许多内部服务来执行其功能,这些服务必须在应用程序启动期间进行注册。 这是通过在 Startup.csConfigureServices 方法中调用 AddRazorPages 来实现的。 没有这个,当你的应用启动时你会得到异常,提醒你调用是必需的。

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

ASP.NET Core 中的 Razor 页面与 MVC

ASP.NET Core 中的 MVC 控制器

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

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

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

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

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

除了一些命名差异之外,ToDoController 看起来与清单 4.1 中的 Razor Page 等价物非常相似。 事实上,在架构上,Razor Pages 和 MVC 本质上是等价的,因为它们都使用 MVC 设计模式。 正如我在下一节中讨论的那样,最明显的区别与文件在项目中的放置位置有关。

Razor页面的好处

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

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

图 4.11 将一个简单的 Razor Pages 项目的文件布局与 MVC 等效项目进行了比较。 使用 Razor 页面意味着当您在特定页面上工作时,在控制器、视图和视图模型文件夹之间上下滚动的次数要少得多。 您需要的一切都在两个文件中找到,即 .cshtml Razor 视图和 .cshtml.cs 页面模型文件。

image

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

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

另一个重要的一点是,Razor Pages 并没有失去 MVC 所具有的任何关注点分离。 Razor Pages 的视图部分仍然只关心渲染 HTML,处理程序是调用应用程序模型的协调器。 唯一真正的区别是缺少 MVC 中的显式视图模型,但如果这对你来说是一个交易破坏者,那么完全可以在 Razor Pages 中模拟这一点。

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

显然,我是 Razor Pages 的粉丝,但这并不是说它们适合所有情况。 在下一节中,我将讨论您可能选择在应用程序中使用 MVC 控制器的一些情况。 请记住,这不是一个非此即彼的选择——可以在同一个应用程序中同时使用 MVC 控制器和 Razor Pages,并且在许多情况下这可能是最佳选择。

何时选择 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 页面和页面处理程序

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

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

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

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

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

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

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

接受页面处理程序的参数

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

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

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

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

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

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

public class SearchModel : PageModel
{
    //SearchService 提供给 SearchModel 以在页面处理程序中使用。
    private readonly SearchService _searchService;
    public SearchModel(SearchService searchService)
    {
        _searchService = searchService;
    }
    
    //用 [BindProperty] 属性修饰的属性将是模型绑定的。
    [BindProperty]
    public BindingModel Input { get; set; }
    //未装饰的属性将不受模型约束。
    public List<Product> Results { get; set; }
    
    //页面处理程序不需要检查模型是否有效。 返回 void 将呈现视图。
    public void OnGet()
    {
    }
    //此页面处理程序中的 max 参数将使用请求中的值进行模型绑定。
    public IActionResult OnPost(int 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 属性也是模型绑定到请求的。

当操作方法使用模型绑定的属性或参数时,它应该始终使用 ModelState.IsValid 检查提供的模型是否有效。 ModelState 属性作为基本 PageModel 类的属性公开,可用于检查所有绑定的属性和参数是否有效。

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

return Page();

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

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

使用 ActionResults 返回响应

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

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

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

其中的每一个,当由 Razor Pages 执行时,将生成一个响应,通过中间件管道发送回并发送给用户。

页面结果和重定向页面结果

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

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

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

NOTFOUNDRESULT 和 STATUSCODERESULT

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

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

使用辅助方法创建 ActionResult 类

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

return new PageResult()

但是,Razor Pages PageModel 基类还提供了许多用于生成响应的辅助方法。 通常使用 Page 方法生成适当的 PageResult,使用 RedirectToPage 方法生成 RedirectToPageResult,或使用 NotFound 方法生成 NotFoundResult

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

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

到目前为止,您应该对 MVC 设计模式以及它与 ASP.NET Core 和 Razor Pages 的关系有一个全面的了解。 Razor 页面上的页面处理程序方法被调用以响应给定的请求,并用于通过返回 IActionResult 来选择要生成的响应类型。

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

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

总结

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