## 4.1 Razor Pages 概述 ## 第四章:ASP.NET Core Razor Pages 父章节领域 - 4.1 Razor Pages 概述 ### 4.1.1 什么是 Razor Pages? Razor Pages 是 ASP.NET Core 中构建 Web UI 的一种简化框架,它基于 ASP.NET MVC 架构,但进行了大幅度的简化,更加专注于以页面为中心的开发模式。与传统的 MVC 模式相比,Razor Pages 将视图(View)和控制器(Controller)的职责整合到一个页面中,使得页面逻辑更加内聚,开发效率更高。 你可以将 Razor Pages 视为一种更轻量级、更直观的 MVC 模式的演变,特别适合于构建以页面为中心的应用程序,例如: * **简单的网站或博客:** 内容相对静态,页面结构清晰。 * **数据录入和管理界面:** 例如后台管理系统,侧重于 CRUD 操作。 * **单页应用程序 (SPA) 的服务端渲染部分:** 处理初始加载和 SEO 优化。 **核心概念:** * **页面 (Page):** Razor Pages 的核心单元是“页面”,每个页面都由两个文件组成: * **`.cshtml` 文件 (Razor 视图):** 负责定义页面的用户界面,使用 Razor 语法编写 HTML 和 C# 代码,用于呈现动态内容。 * **`.cshtml.cs` 文件 (Page Model):** 作为页面的代码后台文件,继承自 `PageModel` 类,负责处理页面的业务逻辑、数据交互和请求处理。 * **Page Model:** `PageModel` 类是 Razor Pages 的关键组成部分,它充当了传统 MVC 中的 Controller 和 ViewModel 的角色。Page Model 负责: * **处理 HTTP 请求:** 通过 Handler 方法(如 `OnGet`、`OnPost` 等)响应不同的 HTTP 请求方法。 * **管理页面数据:** 通过属性将数据传递给 Razor 视图进行渲染。 * **处理用户输入:** 接收和验证表单数据,并进行相应的业务逻辑处理。 **与 MVC 的区别:** | 特性 | MVC (Model-View-Controller) | Razor Pages | |---------------|--------------------------------------------------------------|------------------------------------------------------| | 架构 | 传统的、基于控制器和动作的架构 | 以页面为中心的架构 | | 关注点 | 应用程序的整体架构和模块化 | 单个页面的逻辑和用户体验 | | 文件组织 | 控制器、视图和模型通常分散在不同的文件夹中 | 页面相关的文件(`.cshtml` 和 `.cshtml.cs`)紧密关联 | | 路由 | 基于控制器和动作名称的路由 | 基于文件路径的约定式路由 | | 适用场景 | 大型、复杂的应用程序,需要高度的模块化和可维护性 | 以页面为中心的应用,例如内容网站、表单密集型应用等 | | 学习曲线 | 相对陡峭,需要理解 MVC 的各个组成部分和交互 | 相对平缓,更易于上手,尤其对于前端开发者来说更友好 | **Mermaid 图 - Razor Pages 与 MVC 的概念对比:** ```mermaid graph TD subgraph MVC A[请求 (Request)] --> B(路由 (Routing)); B --> C{控制器 (Controller)}; C --> D{模型 (Model)}; D --> E{视图 (View)}; E --> F[响应 (Response)]; end subgraph Razor Pages G[请求 (Request)] --> H(路由 (Routing)); H --> I{Page Model (.cshtml.cs)}; I --> J{Razor 视图 (.cshtml)}; J --> K[响应 (Response)]; end style MVC fill:#f9f,stroke:#333,stroke-width:2px style Razor Pages fill:#ccf,stroke:#333,stroke-width:2px linkStyle 0,1,2,3,4,5 stroke:#333,stroke-width:1px; linkStyle 6,7,8,9,10 stroke:#333,stroke-width:1px; L[MVC: 控制器和视图分离] --> M[Razor Pages: Page Model 和 Razor 视图紧密结合] style L,M fill:#eee,stroke:#333,stroke-width:1px,stroke-dasharray: 5 5 ``` **图表解释:** * **MVC:** 请求首先到达路由系统,路由系统根据 URL 匹配到相应的控制器和动作。控制器负责处理业务逻辑,与模型交互获取数据,并将数据传递给视图进行渲染,最终生成响应。 * **Razor Pages:** 请求同样到达路由系统,但路由直接映射到 Razor Page 的文件路径。Page Model 负责处理请求逻辑,并将数据传递给 Razor 视图进行渲染,最终生成响应。 从图中可以看出,Razor Pages 将控制器和视图的职责整合到 Page Model 和 Razor 视图中,简化了请求处理流程,减少了组件之间的耦合度,使得页面开发更加直观和高效。 ### 4.1.2 Razor Pages 的优势 选择 Razor Pages 构建 Web 应用,可以带来诸多优势: 1. **更简单的学习曲线:** Razor Pages 的概念更加直观,对于初学者来说更容易上手。特别是对于前端开发者,Razor Pages 的结构和开发模式与前端框架更加相似,学习成本更低。 2. **更好的页面组织性:** 每个页面都包含其自身的所有逻辑和视图代码,使得页面之间的代码更加独立,易于维护和管理。相关的逻辑都集中在一个目录下,方便查找和修改。 3. **更高的开发效率:** 减少了控制器和视图之间的跳转,简化了数据传递流程,使得开发速度更快。对于简单的页面功能,Razor Pages 可以用更少的代码实现相同的功能。 4. **更清晰的代码结构:** Page Model 负责页面逻辑,Razor 视图负责页面呈现,职责划分清晰,代码结构更加简洁易懂。 5. **与 ASP.NET Core 的无缝集成:** Razor Pages 完全构建于 ASP.NET Core 之上,可以充分利用 ASP.NET Core 的所有特性,例如依赖注入、配置、身份验证、授权等。 6. **易于单元测试:** Page Model 是一个独立的类,可以方便地进行单元测试,验证页面的逻辑是否正确。 7. **适合页面驱动的应用程序:** 对于大多数 Web 应用来说,最终都是以页面为单位呈现给用户的,Razor Pages 这种以页面为中心的模型更加符合实际需求。 ### 4.1.3 Razor Pages 的基本结构和代码实践 接下来,我们将通过一个简单的示例来演示 Razor Pages 的基本结构和代码实践。 **1. 创建 Razor Pages 项目:** 首先,你需要创建一个 ASP.NET Core Razor Pages 项目。你可以使用 Visual Studio 或 .NET CLI 工具来创建。 **使用 .NET CLI:** ```bash dotnet new webapp -o RazorPagesDemo cd RazorPagesDemo ``` **2. 查看默认的 Razor Page:** 在项目创建完成后,你会在 `Pages` 文件夹下看到默认的 Razor Pages,例如 `Index.cshtml` 和 `Index.cshtml.cs`。 **`Pages/Index.cshtml` (Razor 视图):** ```cshtml @page @model RazorPagesDemo.Pages.IndexModel @{ ViewData["Title"] = "Home page"; }
Welcome
Learn about building Web apps with ASP.NET Core.
当前时间: @Model.CurrentTime
``` **`Pages/Index.cshtml.cs` (Page Model):** ```csharp using Microsoft.AspNetCore.Mvc.RazorPages; namespace RazorPagesDemo.Pages { public class IndexModel : PageModel { private readonly ILogger _logger; public IndexModel(ILogger logger) { _logger = logger; } public string CurrentTime { get; set; } public void OnGet() { CurrentTime = DateTime.Now.ToString(); } } } ``` **代码详解:** * **`@page` 指令 (Razor 视图 - `.cshtml`):** `@page` 指令告诉 Razor 引擎,这是一个 Razor Page,而不是一个普通的 Razor View。它使得该文件可以直接处理 HTTP 请求。 * **`@model RazorPagesDemo.Pages.IndexModel` 指令 (Razor 视图 - `.cshtml`):** `@model` 指令声明了当前 Razor 视图所关联的 Page Model 类型,这里是 `RazorPagesDemo.Pages.IndexModel`。这使得 Razor 视图可以访问 Page Model 中的属性和方法。 * **`ViewData["Title"] = "Home page";` (Razor 视图 - `.cshtml`):** 设置 `ViewData` 字典中的 `Title` 键的值,用于设置页面的标题。 * **`@Model.CurrentTime` (Razor 视图 - `.cshtml`):** 使用 Razor 语法访问 Page Model 中的 `CurrentTime` 属性,并将其值渲染到 HTML 中。 * **`IndexModel : PageModel` (Page Model - `.cshtml.cs`):** `IndexModel` 类继承自 `PageModel` 基类,使其成为一个 Razor Page 的 Page Model。 * **`public string CurrentTime { get; set; }` (Page Model - `.cshtml.cs`):** 定义了一个公共属性 `CurrentTime`,用于存储当前时间,并将其传递给 Razor 视图。 * **`public void OnGet()` (Page Model - `.cshtml.cs`):** `OnGet` 方法是一个 Handler 方法,用于处理 HTTP GET 请求。当用户访问 `/` 或 `/Index` 路径时,会执行 `OnGet` 方法。 * 在 `OnGet` 方法中,我们将当前时间赋值给 `CurrentTime` 属性。 **3. 运行应用程序:** 运行你的 ASP.NET Core Razor Pages 应用程序 (可以使用 `dotnet run` 命令或在 Visual Studio 中运行)。访问 `http://localhost:xxxx` (端口号可能不同),你将看到 "Welcome" 页面,并且页面上会显示当前时间。 **4. 添加一个新的 Razor Page:** 假设我们需要添加一个 "About" 页面。 * **在 `Pages` 文件夹下创建 `About.cshtml` 文件:** ```cshtml @page @model RazorPagesDemo.Pages.AboutModel @{ ViewData["Title"] = "About Us"; }
About Us
This is a simple Razor Pages demo application.
``` * **在 `Pages` 文件夹下创建 `About.cshtml.cs` 文件:** ```csharp using Microsoft.AspNetCore.Mvc.RazorPages; namespace RazorPagesDemo.Pages { public class AboutModel : PageModel { public void OnGet() { } } } ``` **代码解释:** * 我们创建了 `About.cshtml` 和 `About.cshtml.cs` 两个文件,分别对应 Razor 视图和 Page Model。 * `AboutModel` 类继承自 `PageModel`,并包含一个空的 `OnGet` 方法。 * `About.cshtml` 文件使用了 `@page` 和 `@model` 指令,并显示了 "About Us" 标题和一段简单的文本。 **5. 访问 "About" 页面:** 运行应用程序,访问 `http://localhost:xxxx/About`,你将看到 "About Us" 页面。 **路由约定:** Razor Pages 使用约定式路由。默认情况下,Razor Pages 的路由路径与 `Pages` 文件夹下的文件路径相对应。 * `Pages/Index.cshtml` -> `/` 或 `/Index` * `Pages/About.cshtml` -> `/About` * `Pages/Contact/Details.cshtml` -> `/Contact/Details` 你可以通过 `@page "{routeTemplate}"` 指令在 Razor 视图中自定义路由模板,但这超出了本概述章节的范围。 ### 4.1.4 Razor Pages 的 Handler 方法 Handler 方法是 Page Model 中用于处理特定 HTTP 请求的方法。Razor Pages 约定了一系列 Handler 方法的命名规则: * **`OnGet()`:** 处理 HTTP GET 请求。 * **`OnPost()`:** 处理 HTTP POST 请求。 * **`OnPut()`:** 处理 HTTP PUT 请求。 * **`OnDelete()`:** 处理 HTTP DELETE 请求。 * **`OnPatch()`:** 处理 HTTP PATCH 请求。 以及更细化的 Handler 方法,例如: * **`OnGet{HandlerName}()`:** 例如 `OnGetDetails()`, `OnGetEdit()`,用于处理带有特定 Handler 名称的 GET 请求,通常用于处理不同的页面操作。 * **`OnPost{HandlerName}()`:** 例如 `OnPostSubmit()`, `OnPostDelete()`,用于处理带有特定 Handler 名称的 POST 请求,常用于处理表单提交或特定操作。 **示例 - 使用 Handler 方法处理表单提交:** **`Pages/Contact.cshtml` (Razor 视图):** ```cshtml @page @model RazorPagesDemo.Pages.ContactModel @{ ViewData["Title"] = "Contact Us"; }
Contact Us
Name:
Email:
Submit @if (!string.IsNullOrEmpty(Model.Message)) {
@Model.Message
} ``` **`Pages/Contact.cshtml.cs` (Page Model):** ```csharp using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using System.ComponentModel.DataAnnotations; namespace RazorPagesDemo.Pages { public class ContactModel : PageModel { [BindProperty] // 绑定表单数据到 ContactForm 属性 public ContactFormModel ContactForm { get; set; } public string Message { get; set; } public void OnGet() { } public IActionResult OnPost() // 处理 POST 请求 { if (!ModelState.IsValid) // 验证模型状态 { return Page(); // 如果验证失败,返回当前页面,显示验证错误信息 } // 在这里处理表单提交逻辑,例如发送邮件、保存数据等 Message = $"Thank you, {ContactForm.Name}! We have received your message."; return Page(); // 返回当前页面,显示成功消息 } } public class ContactFormModel { [Required(ErrorMessage = "Name is required.")] public string Name { get; set; } [Required(ErrorMessage = "Email is required.")] [EmailAddress(ErrorMessage = "Invalid email address.")] public string Email { get; set; } } } ``` **代码详解:** * **`@model RazorPagesDemo.Pages.ContactModel` (Razor 视图):** 关联 Page Model `ContactModel`。 * **`` (Razor 视图):** 定义一个 POST 表单,当提交表单时,会发送 POST 请求到当前页面。 * **`asp-for="ContactForm.Name"` 和 `asp-for="ContactForm.Email"` (Razor 视图):** Tag Helpers,用于将表单输入元素绑定到 Page Model 的 `ContactForm` 属性的 `Name` 和 `Email` 属性。 * **`asp-validation-for="ContactForm.Name"` 和 `asp-validation-for="ContactForm.Email"` (Razor 视图):** Validation Tag Helpers,用于显示模型验证错误信息。 * **`[BindProperty]` (Page Model - `.cshtml.cs`):** `[BindProperty]` 特性标记 `ContactForm` 属性,表示该属性可以接收来自表单提交的数据绑定。 * **`ContactFormModel` 类 (Page Model - `.cshtml.cs`):** 定义了用于接收表单数据的模型类,并使用 `[Required]` 和 `[EmailAddress]` 等 Data Annotations 特性进行数据验证。 * **`public IActionResult OnPost()` (Page Model - `.cshtml.cs`):** `OnPost` 方法处理 POST 请求。 * `if (!ModelState.IsValid)` 检查模型状态是否有效,如果验证失败,`ModelState.IsValid` 为 `false`。 * `return Page();` 返回当前页面,用于重新渲染页面,并显示验证错误信息。 * 如果验证成功,则执行表单提交逻辑,并设置 `Message` 属性,用于显示成功消息。 **运行应用程序,访问 `http://localhost:xxxx/Contact`,你将看到联系表单。填写表单并提交,如果验证成功,你将看到成功消息。如果验证失败,你将看到验证错误信息。** ### 4.1.5 Razor Pages 的适用场景和局限性 **适用场景:** * **以页面为中心的应用程序:** 例如内容网站、博客、文档站点、后台管理系统等。 * **快速原型开发:** Razor Pages 能够快速搭建页面原型,验证想法。 * **小型到中型 Web 应用程序:** 对于规模适中的应用程序,Razor Pages 可以提供良好的开发效率和可维护性。 * **团队中前端开发者主导的项目:** Razor Pages 的结构更贴近前端开发模式,更容易被前端开发者接受和使用。 **局限性:** * **大型、复杂的应用程序:** 对于需要高度模块化、复杂业务逻辑和团队协作的大型应用程序,传统的 MVC 模式可能更适合,因为 MVC 提供了更清晰的职责分离和更强的架构灵活性。 * **重用性和共享性:** 虽然 Razor Pages 也支持布局页 (_Layout) 和部分视图 (_PartialView),但在组件重用和逻辑共享方面,可能不如 MVC 的控制器和 Action Filter 机制灵活。 * **测试的复杂性:** 虽然 Page Model 可以进行单元测试,但涉及到页面交互和 UI 逻辑的集成测试可能相对复杂。 **选择 Razor Pages 还是 MVC?** 选择 Razor Pages 还是 MVC,取决于你的项目需求和团队情况。 * **选择 Razor Pages:** 如果你的应用以页面为中心,注重开发效率和页面组织性,团队中前端开发者占主导地位,或者项目规模不大,Razor Pages 是一个很好的选择。 * **选择 MVC:** 如果你的应用规模较大,业务逻辑复杂,需要高度的模块化和可维护性,团队中后端开发者占主导地位,或者需要更强的架构灵活性和组件重用性,MVC 可能是更合适的选择。 **总结:** Razor Pages 是 ASP.NET Core 中一种强大的、以页面为中心的 Web UI 开发模型。它简化了 Web 开发流程,提高了开发效率,并提供了良好的页面组织性。理解 Razor Pages 的核心概念、优势和适用场景,可以帮助你更好地选择合适的开发模型,并构建高效、可维护的 ASP.NET Core Web 应用程序。本章节作为 Razor Pages 的概述,为你后续深入学习 Razor Pages 的其他特性和高级用法奠定了基础。在接下来的章节中,我们将继续探索 Razor Pages 的更多细节,例如路由、模型绑定、表单处理、验证、布局、组件等等。