引入
Razor Pages 是 ASP.NET Core 2.0 中引入的 ASP.NET Core MVC 的一个新方面。它提供了一种**“基于页面”的方法**,用于在 ASP.NET Core 中构建服务器端呈现的应用程序,并且可以与“传统”MVC 或 Web API 控制器共存。在这篇文章中,我将介绍 Razor Pages、入门基础知识以及 Razor Pages 与 MVC 的不同之处。
在 MVC 中,控制器用于将相似的操作组合在一起(其实就是MVC默认的路由规则要求,在一个控制器controller 类下有多个方法,每个方法对应一个razor页面)。收到请求时,路由会将请求定向到单个操作方法。此方法通常执行一些处理,并返回一个IActionResult,通常是 aViewResult或 a RedirectResult。如果 a返回,则使用提供的视图模型呈现ViewResultRazor视图。
MVC 提供了很大的灵活性,因此将操作分组到控制器中可以是高度自由的,但您通常会**以某种方式对相关的操作进行分组**,例如通过 URL 路由或按功能。例如,您可以按域组件进行分组,以便在电子商务应用程序中,与“产品”相关的操作将位于 中ProductController,而“购物车”操作将位于CartController. 或者,可以根据技术方面对动作进行分组;例如,控制器上的所有操作共享一组通用的授权要求。
您会发现**一个常见的模式**是在控制器中包含成对的相关操作。在您使用 HTML 表单时尤其如此,您通常需要一个操作来处理初始GET请求,而另一个操作来处POST理请求。这两个操作都使用相同的 URL 路由和相同的 Razor 视图。从用户(或开发人员)的角度来看,它们在逻辑上是同一“页面”的两个方面。
在某些情况下,您可能会发现您的控制器充满了这些操作方法对。例如,AccountControllerMVC 应用程序的默认 ASP.NET Core 标识包含许多这样的对:
GET 和 POST 对动作高度耦合,因为它们都返回相同的视图模型,可能需要类似的初始化逻辑,并使用相同的 Razor 视图。这对操作也与它们所在的整体控制器相关(它们都与身份和帐户相关),但它们之间的关系更密切。
Razor Pages 提供与传统 MVC 大致相同的功能,但通过利用这种配对使用稍微不同的模型。每条路由(每对动作)都成为一个单独的 Razor 页面,而不是将许多相似的动作组合在一个控制器下。该页面可以有多个处理程序,每个处理程序都响应不同的 HTTP 动词,但使用相同的视图。因此,上面的相同身份AccountController可以在 Razor 页面中重写,如下所示。事实上,从 ASP.NET Core 2.1 开始,新的项目模板使用 Razor Pages for Identity,即使在 MVC 应用程序中也是如此。
Razor Pages 具有高度凝聚力的优势。与应用程序中给定页面相关的所有内容都在一个地方(一个动作都在一个页面中得到解决)。与 MVC 控制器相比,其中一些动作高度相关,但控制器作为一个整体的凝聚力较低。
使用 Razor 页面的另一个好指标是,当您的 MVC 控制器仅返回 Razor 视图而无需进行大量处理时。一个经典的例子是HomeController来自 ASP.NET Core 2.0 模板,其中包括四个操作:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
这些动作并不是真正相关的,但是每个动作都需要一个控制器,并且HomeController放置它们是一个比较方便的位置。Razor Pages 等效项将Index(Home) About 、Contact和Error页面放在根目录中,删除它们之间的隐式链接。作为额外的奖励,与About页面相关的所有内容(例如)都可以在文件About.cshtml和中找到About.cshtml.cs,它们一起位于磁盘和解决方案资源管理器中。这与控制器、视图模型和视图文件通常位于完全不同的文件夹中的 MVC 方法形成对比。
![image-20221102191457678](https://i-blog.csdnimg.cn/blog_migrate/7fa1e75dc169532b74d202a0ddd70480.png)
创建 Razor 页面,它与传统的 Razor 视图有何不同?
接下来,如何创建 Razor 页面,它与传统的 Razor 视图有何不同?
Razor 页面模型
Razor Pages 构建在 MVC 之上,但它们使用的范式与 MVC 模式略有不同。使用 MVC,控制器通常为操作提供逻辑和行为,最终生成包含用于呈现视图的数据的视图模型。Razor Pages 采用了稍微不同的方法,即使用Page Model。
与 MVC 相比,页面模型**既充当微型控制器,又充当视图的视图模型。它负责页面的行为和公开用于生成视图的数据**。这种模式更接近于某些桌面和移动框架中使用的模型-视图-视图模型 (MVVM)模式,尤其是当业务逻辑被推出页面模型并进入您的“业务”模型时。
从技术上讲,Razor 页面与 Razor 视图非常相似,只是它@page在文件顶部有一个指令:
@page
<div>The time is @DateTime.Now</div>
![img](https://www.codeproject.com/KB/aspnet/1208668/RazorPage.png)
与 Razor 视图一样,Razor 页面中的任何 HTML 都会呈现给客户端,您可以使用该@符号呈现 C# 值或使用 C# 控制结构。
添加@page是公开页面所需的全部内容,但此页面尚未使用页面模型。更典型的是,您创建一个派生自文件PageModel并将其与cshtml文件关联的类。如果您愿意,您可以将您的PageModel视图和 Razor 视图包含在同一个cshtml文件中,但最佳做法是将其保存PageModel在“代码隐藏”文件中,并且仅在cshtml文件中包含演示数据。按照惯例,如果您的Razor页面被调用MyPage.cshtml,则代码隐藏文件应命名为MyPage.cshtml.cs:
该类PageModel是 Razor 视图的页面模型。呈现 Razor 页面时,在视图上公开的属性在视图PageModel中可用。.cshtml例如,您可以在Index.cshtml.cs文件中公开当前时间的属性:
using System;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel: PageModel
{
public DateTime CurrentTime => DateTime.UtcNow;
}
Index.cshtml并使用标准 Razor 语法将其呈现在您的文件中:
@page
@model IndexModel
<div>The current time is @Model.CurrentTime.ToShortTimeString()</div>
如果你熟悉 Razor 视图,这应该很熟悉。您使用在属性@model上公开的指令声明模型的类型。Model不同之处在于,您的 MVC 控制器不是传入类型为 View Model 的 View Model,而是将IndexModel其PageModel本身作为Model属性公开。
Razor 页面中的路由
Razor Pages 与 MVC 类似,混合使用约定、配置和声明性指令来控制应用程序的行为方式。它们在底层使用与 MVC 相同的路由基础设施;不同之处在于路由的配置方式。
对于 MVC 和 Web API,您可以使用属性或基于约定的路由将传入 URL 与控制器和操作相匹配。
对于 Razor 页面,**磁盘上文件的路径**用于计算可以访问页面的 URL。按照惯例,所有 Razor 页面都嵌套在Pages目录中。
例如,如果您在应用程序中创建一个 Razor 页面/Pages/MyFolder/Test.cshtml
,它将在 URL 处公开/MyFolder/Test
。这对于使用 Razor 页面来说绝对是一个积极的特性——导航和可视化应用程序公开的 URL 就像查看文件结构一样简单。
话虽如此,Razor Page 路由是完全可定制的;如果您需要在与磁盘上的路径不对应的路由上公开您的页面,只需在指令中提供一个路由模板。@page这也可以包括其他路由参数,如下所示:
@page "/customroute/customized/{id?}"
Razor Pages 中的模型绑定
在 MVC 中,控制器中的操作的方法参数通过匹配 URL、查询字符串或请求正文中的值来绑定到传入请求(有关详细信息,请参阅文档)。在 Razor Pages 中,传入的请求被绑定到PageModel的属性。
出于安全原因,您必须通过装饰属性以与属性绑定来明确选择要绑定的[BindProperty]
属性:
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
[BindProperty]
public string Search { get;set; }
public DateTime CurrentTime { get; set; };
}
在此示例中,Search
属性将绑定到请求,因为它用 装饰[BindProperty]
,但CurrentTime
不会被绑定。对于 GET 请求,您必须更进一步并在SupportsGet
属性上设置属性:
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
[BindProperty(SupportsGet = true)]
public string Search { get;set; }
}
如果您要绑定复杂的模型,例如回发表单,那么在[BindProperty]任何地方添加属性都会变得乏味。相反,我喜欢创建一个属性作为“输入模型”并用[BindProperty].(输入模型成为一个嵌套类) 这使您的PageModel公共表面区域保持明确和可控。这种方法的一个常见扩展是使您的输入模型成为一个嵌套类。这通常是有道理的,因为您通常不想在应用程序的其他地方使用您的 UI 层模型:
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
public class IndexModel : PageModel
{
public bool IsEmailConfirmed { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[Required, EmailAddress]
public string Email { get; set; }
[Required, Phone, Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
}
在此示例中,只有属性Input绑定到传入请求。这使用嵌套InputModel类来定义要绑定的所有预期值。如果需要绑定其他值,可以添加其他属性到InputModel.
使用 Razor 页面处理程序处理多个 HTTP 动词
Razor Pages 的主要卖点之一是它们可以使用 MVC 控制器带来额外的凝聚力。通过使用页面处理程序响应请求,单个 Razor 页面包含与给定 Razor 视图关联的所有 UI 代码。
页面处理程序类似于 MVC 中的操作方法。当 Razor 页面收到请求时,会根据传入的请求和处理程序名称选择运行单个处理程序。处理程序通过命名约定匹配On{Verb}[Async]
,其中{Verb}是 HTTP 方法,并且[async]是可选的。例如:
OnPost
和OnPostAsync
响应POST
请求运行OnGet
和OnGetAsync
响应GET
请求(以及可选HEAD的 ASP.NET Core 2.1+ 中的请求)运行。
OnGet
对于 HTML 表单,有一个显示初始空表单的处理程序和一个OnPost
处理来自客户端的返回的处理程序是很常见POST
的。例如,以下表单显示了更新用户显示名称的 Razor 页面的代码隐藏。
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
public class UpdateDisplayNameModel : PageModel
{
private readonly IUserService _userService;
public IndexModel(IUserService userService)
{
_userService = userService;
}
[BindProperty]
public InputModel Input { get; set; }
public void OnGet()
{
Input.DisplayName = _userService.GetDefaultDisplayName(); //显示初始空表单的处理程序
}
public async Task<IActionResult> OnPostAsync() //处理来自客户端的返回
{
if (!ModelState.IsValid)
{
return Page();
}
await _userService.UpdateDisplayName(User, Input.DisplayName);
return RedirectToPage("/Index"); //**在功能上等同于 MVC 的RedirectToAction()方法**。
}
public class InputModel
{
[Required, StringLength(50)]
public string DisplayName { get; set; }
}
}
这个 Razor Page 使用了一个虚构IUserService的注入PageModel构造函数。页面处理程序在OnGet
最初请求表单时运行,并将默认显示名称设置为从IUserService. 表格被发送给客户,客户填写详细信息并将其发回。
OnPostAsync处理程序响应 , 运行,POST并遵循与 MVC 操作方法类似的模式。您应该首先使用 来检查模型验证PageModel是否通过,如果没有则使用 重新显示表单。在语义上等价于 MVC 控制器中的方法;它用于渲染视图并返回响应。ModelState.IsValidPage()Page()View()
如果PageModel有效,则表单使用提供的DisplayName值来更新当前登录用户的名称,User。PageModel提供对许多与基类相同的属性的访问,Controller例如HttpContext, Request,在本例中为User. RedirectToPage()最后,处理程序使用该方法重定向到另一个 Razor 页面。这在功能上等同于 MVC 的RedirectToAction()方法。
当表单只有一个可能的角色时,OnGet和处理程序对很常见,但也可以有一个 Razor Page 和多个处理程序用于同一个动词。要创建命名处理程序,请使用命名约定。例如,也许我们想向Razor 页面添加一个处理程序,允许用户将其用户名重置为默认值。我们可以将以下处理程序添加到现有的 Razor 页面:On{Verb}{Handler}[Async]
public async Task<IActionResult> OnPostResetNameAsync()
{
await _userService.ResetDisplayName(User);
return RedirectToPage("/Index");
}
要**调用处理程序,请在查询字符串中传递处理程序(方法)名称POST**,例如?handler=resetName
。这确保调用命名处理程序而不是默认OnPostAsync处理程序。
如果您不喜欢在此处使用查询字符串,则可以使用自定义路由并将处理程序名称包含在路径段中。
本节展示了 和 的处理程序GET,POST, 但也可以为其他 HTTP 动词(如DELETE、PUT和PATCH). HTML 表单通常不使用这些动词,因此在面向页面的 Razor Pages 应用程序中通常不需要。但是,如果您出于某种原因需要 API 调用它们,它们遵循与其他页面处理程序相同的命名约定和行为。
使用Handlers实现多个GET、POST Action
默认情况下,Razor页面设计为具有单个
OnGetAsync
和OnPostAsync
Action方法;如果您想在单个页面中具有不同的Action,则需要使用所谓的Handler。如您的页面有AJAX回调、多个表单提交或其它场景,则需要使用它。例如,如果您使用Kendo Grid并希望通过AJAX调用加载Grid数据,则需要使用Handler来处理该AJAX调用。任何类型的单页面应用程序将使用大量Handler,或者您将所有这些AJAX调用指向MVC控制器。
我在页面中添加了一个名为
OnGetHelloWorldAsync()
的方法,我们该怎么调用它?从我的研究来看, 调用Handler有三种不同的方式:
- Querystring – 示例:“/managepage/2177/?handler=helloworld”
- 为视图中的定义路由:
@page"{handler?}"
,然后在Url中包括“/helloworld”- 在视图中定义提交按钮 - 示例:
可以在这里了解更多有关单页面多个Handlers 的方式。
在 Razor 页面中使用标签助手TagHelper
当您使用 MVC 操作和视图时,ASP.NET Core 提供了各种标记帮助asp-action器,例如asp-controller用于从 Razor 视图生成指向您的操作的链接。Razor 页面具有等效的标记帮助程序,您可以使用其中asp-page生成指向特定 Razor 页面的路径,并asp-page-handler设置特定的处理程序。
例如,您可以使用asp-page和asp-page-handler标签在表单中创建一个“重置名称”按钮:
<button asp-page="/Index" asp-page-handler="ResetName" type="submit">Reset Display Name</button>
概括
Razor 页面是 ASP.NET Core MVC 的一个新方面,在 ASP.NET Core 2.0 中引入。它们构建在现有 ASP.NET Core 基元之上,并提供与传统 MVC 相同的整体功能,但具有基于页面的模型。对于许多应用程序,使用 a 的基于页面的方法PageModel可以产生比传统 MVC 更具凝聚力的代码。Razor Pages 可以与传统的 MVC 或 Web API 控制器在同一应用程序中无缝使用,因此您只需要在合适的地方使用它。
如果您使用 Razor 创建新应用程序,我强烈建议您将 Razor Pages 视为默认方法。一开始对于有经验的 MVC 开发人员可能会觉得奇怪,但我对改进的开发体验感到惊喜。对于现有的 MVC 应用程序,添加新的 Razor 页面很容易,但不太值得迁移整个 MVC 应用程序来使用它们。它们在功能上与 MVC 相同,因此主要优点是更方便常见开发任务的工作流。