ASP.NET Core Razor 页面入门

Razor Pages 是 ASP.NET Core 2.0 中引入的 ASP.NET Core MVC 的一个新方面。它提供了一种“基于页面”的方法,用于在 ASP.NET Core 中构建服务器端呈现的应用程序,并且可以与“传统”MVC 或 Web API 控制器共存。在这篇文章中,我将介绍 Razor Pages、入门基础知识以及 Razor Pages 与 MVC 的不同之处。

剃刀页面与 MVC

如果您使用 ASP.NET Core 构建服务器端呈现的应用程序,那么您将熟悉传统的模型-视图-控制器 (MVC) 模式。Razor Pages 在 MVC 之上提供了一个抽象,这可以使它更适合一些基于页面的应用程序。

在 MVC 中,控制器用于将相似的操作组合在一起。收到请求时,路由会将请求定向到单个操作方法。此方法通常执行一些处理,并返回一个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、、、ContactError页面放在根目录中,删除它们之间的隐式链接。作为额外的奖励,与About页面相关的所有内容(例如)都可以在文件About.cshtml和中找到About.cshtml.cs,它们一起位于磁盘和解决方案资源管理器中。这与控制器、视图模型和视图文件通常位于完全不同的文件夹中的 MVC 方法形成对比。

但是,这两种方法在功能上是相同的,那么您应该选择哪一种呢?

什么时候应该使用 Razor Pages?

重要的是要意识到您不必全力以赴使用 Razor Pages。Razor Pages 使用与传统 MVC 完全相同的基础架构,因此您可以将 Razor Pages 与 MVC 和 Web API 控制器混合在同一个应用程序中。Razor Pages 还使用与传统 MVC 相同的 ASP.NET Core 原语,因此您仍然可以获得模型绑定、验证和操作结果。

从可维护性的角度来看,我发现 Razor Pages 提供的额外凝聚力使其更适合新开发。不必在控制器、视图模型和视图文件之间来回跳转,令人耳目一新!

话虽如此,在某些情况下,最好还是坚持使用传统的 MVC 控制器:

  • 当你的控制器上有很多MVC 动作过滤器时。您也可以在 Razor Pages 中使用过滤器,但它们通常提供的细粒度控制不如传统 MVC。例如,您不能将 Razor 页面过滤器应用于 Razor 页面中的单个处理程序(例如,GETvsPOST处理程序)。
  • 当您的 MVC 控制器不呈现视图时。Razor 页面专注于“页面”模型,您在其中为用户呈现视图。如果你的控制器要么是 Web API 控制器,要么不是为提供用户导航的页面而设计的,那么 Razor 页面就没有任何意义。
  • 当您的控制器已经高度内聚时,将动作方法集中在一个文件中是有意义的。

另一方面,在某些情况下 Razor Pages 真的很出色:

  • 当您的操作方法几乎没有逻辑并且只是返回视图时(例如,HomeController前面显示的)。
  • 当您的 HTML 表单具有成对的GETPOST动作时。Razor Pages 使每一对都成为一个有凝聚力的页面,我发现在开发时需要更少的认知开销,而不必在多个文件之间跳转。
  • 当您以前使用ASP.NET 网页 (WebMatrix)时。该框架提供了一个基于页面的轻量级模型,但它与 ASP.NET 完全分离。相比之下,Razor Pages 具有类似级别的简单性,但在需要时还具有 ASP.NET Core 的全部功能。

现在您已经看到了 MVC 和 Razor Pages 之间的高级差异,是时候深入了解细节了。如何创建 Razor 页面,它与传统的 Razor 视图有何不同?

剃刀页面模型

Razor Pages 构建在 MVC 之上,但它们使用的范式与 MVC 模式略有不同。使用 MVC,控制器通常为操作提供逻辑和行为,最终生成包含用于呈现视图的数据的视图模型。Razor Pages 采用了稍微不同的方法,即使用Page Model

与 MVC 相比,页面模型既充当微型控制器,又充当视图的视图模型。它负责页面的行为和公开用于生成视图的数据。这种模式更接近于某些桌面和移动框架中使用的模型-视图-视图模型 (MVVM)模式,尤其是当业务逻辑被推出页面模型并进入您的“业务”模型时。

从技术上讲,Razor 页面与 Razor 视图非常相似,只是它@page在文件顶部有一个指令:

@page

<div>The time is @DateTime.Now</div>

与 Razor 视图一样,Razor 页面中的任何 HTML 都会呈现给客户端,您可以使用该@符号呈现 C# 值或使用 C# 控制结构。有关 Razor 语法的完整参考指南,请参阅文档

添加@page是公开页面所需的全部内容,但此页面尚未使用页面模型。更典型的是,您创建一个派生自文件PageModel并将其与cshtml文件关联的类。如果您愿意,您可以将您的PageModel视图和 Razor 视图包含在同一个cshtml文件中,但最佳做法是将其保存PageModel在“代码隐藏”文件中,并且仅在cshtml文件中包含演示数据。按照惯例,如果您的剃须刀页面被调用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,而是将IndexModelPageModel本身作为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?}" 

该页面将在 URL 处公开/customroute/customized,并且它还可以id在 URL 中绑定可选段,/customroute/customized/123例如。id可以使用模型绑定将值绑定到PageModel属性。

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]是可选的。例如:

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");
    }

    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值来更新当前登录用户的名称,UserPageModel提供对许多与基类相同的属性的访问,Controller例如HttpContextRequest,在本例中为UserRedirectToPage()最后,处理程序使用该方法重定向到另一个 Razor 页面。这在功能上等同于 MVCRedirectToAction()方法。

当表单只有一个可能的角色时,OnGet和处理程序对很常见,但也可以有一个 Razor Page 和多个处理程序用于同一个动词。要创建命名处理程序,请使用命名约定。例如,也许我们想向Razor 页面添加一个处理程序,允许用户将其用户名重置为默认值。我们可以将以下处理程序添加到现有的 Razor 页面:OnPostOn{Verb}{Handler}[Async]UpdateDisplayNameResetName

public async Task<IActionResult> OnPostResetNameAsync()
{
    await _userService.ResetDisplayName(User);
    return RedirectToPage("/Index");
}

要调用处理程序,请在 的查询字符串中传递处理程序名称POST,例如?handler=resetName。这确保调用命名处理程序而不是默认OnPostAsync处理程序。如果您不喜欢在此处使用查询字符串,则可以使用自定义路由并将处理程序名称包含在路径段中

本节展示了 和 的处理程序GETPOST但也可以为其他 HTTP 动词(如DELETEPUTPATCH. HTML 表单通常不使用这些动词,因此在面向页面的 Razor Pages 应用程序中通常不需要。但是,如果您出于某种原因需要 API 调用它们,它们遵循与其他页面处理程序相同的命名约定和行为。

在 Razor 页面中使用标签助手

当您使用 MVC 操作和视图时,ASP.NET Core 提供了各种标记帮助asp-action器,例如asp-controller用于从 Razor 视图生成指向您的操作的链接。Razor 页面具有等效的标记帮助程序,您可以使用其中asp-page生成指向特定 Razor 页面的路径,并asp-page-handler设置特定的处理程序。

例如,您可以使用asp-pageasp-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 相同,因此主要优点是更方便常见开发任务的工作流。

其他资源

Learn Razor PagesMicrosoft MVP Mike Brind的教程网站设置。除了官方文档之外,我强烈推荐这是一个很好的资源。

Andrew Lock 是 Microsoft MVP 和Manning 的 ASP.NET Core in Action 的作者。可以在 Twitter 上@andrewlocknet或通过他的博客https://andrewlock.net联系到他。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值