ASP.NET Core 中已搭建基架的 Razor 页面Scaffolded Razor Pages in ASP.NET Core

作者:Rick AndersonBy Rick Anderson

本教程介绍上一教程中通过搭建基架创建的 Razor 页面。This tutorial examines the Razor Pages created by scaffolding in the previous tutorial.

查看或下载示例。View or download sample.

“创建”、“删除”、“详细信息”和“编辑”页面The Create, Delete, Details, and Edit pages

检查 Pages/Movies/Index.cshtml.cs 页面模型:Examine the Pages/Movies/Index.cshtml.cs Page Model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Models;

namespace RazorPagesMovie.Pages.Movies
{
    public class IndexModel : PageModel
    {
        private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;

        public IndexModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
        {
            _context = context;
        }

        public IList<Movie> Movie { get;set; }

        public async Task OnGetAsync()
        {
            Movie = await _context.Movie.ToListAsync();
        }
    }
}

Razor 页面派生自 PageModelRazor Pages are derived from PageModel. 按照约定,PageModel 派生的类称为 <PageName>ModelBy convention, the PageModel-derived class is called <PageName>Model. 此构造函数使用依赖关系注入RazorPagesMovieContext 添加到页。The constructor uses dependency injection to add the RazorPagesMovieContext to the page. 所有已搭建基架的页面都遵循此模式。All the scaffolded pages follow this pattern. 请参阅异步代码,了解有关使用实体框架的异步编程的详细信息。See Asynchronous code for more information on asynchronous programing with Entity Framework.

对页面发出请求时,OnGetAsync 方法向 Razor 页面返回影片列表。When a request is made for the page, the OnGetAsync method returns a list of movies to the Razor Page. 在 Razor 页面上调用 OnGetAsyncOnGet 以初始化页面状态。OnGetAsync or OnGet is called on a Razor Page to initialize the state for the page. 在这种情况下,OnGetAsync 将获得影片列表并显示出来。In this case, OnGetAsync gets a list of movies and displays them.

OnGet 返回 voidOnGetAsync 返回 Task 时,不使用任何返回方法。When OnGet returns void or OnGetAsync returnsTask, no return method is used. 当返回类型是 IActionResultTask<IActionResult> 时,必须提供返回语句。When the return type is IActionResult or Task<IActionResult>, a return statement must be provided. 例如,Pages/Movies/Create.cshtml.cs OnPostAsync 方法:For example, the Pages/Movies/Create.cshtml.cs OnPostAsync method:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

检查 Pages/Movies/Index.cshtml Razor 页面: Examine the Pages/Movies/Index.cshtml Razor Page:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-page="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Title)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].ReleaseDate)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Genre)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Movie[0].Price)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model.Movie) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ReleaseDate)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Genre)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Price)
            </td>
            <td>
                <a asp-page="./Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-page="./Details" asp-route-id="@item.ID">Details</a> |
                <a asp-page="./Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

Razor 可以从 HTML 转换为 C# 或 Razor 特定标记。Razor can transition from HTML into C# or into Razor-specific markup. @ 符号后跟 Razor 保留关键字时,它会转换为 Razor 特定标记,否则会转换为 C#。When an @ symbol is followed by a Razor reserved keyword, it transitions into Razor-specific markup, otherwise it transitions into C#.

@page Razor 指令将文件转换为一个 MVC 操作,这意味着它可以处理请求。The @page Razor directive makes the file into an MVC action, which means that it can handle requests. @page 必须是页面上的第一个 Razor 指令。@page must be the first Razor directive on a page. @page 是转换到 Razor 特定标记的一个示例。@page is an example of transitioning into Razor-specific markup. 有关详细信息,请参阅 Razor 语法See Razor syntax for more information.

检查以下 HTML 帮助程序中使用的 Lambda 表达式:Examine the lambda expression used in the following HTML Helper:

@Html.DisplayNameFor(model => model.Movie[0].Title))

DisplayNameFor HTML 帮助程序检查 Lambda 表达式中引用的 Title 属性来确定显示名称。The DisplayNameFor HTML Helper inspects the Title property referenced in the lambda expression to determine the display name. 检查 Lambda 表达式(而非求值)。The lambda expression is inspected rather than evaluated. 这意味着当 modelmodel.Moviemodel.Movie[0]null 或为空时,不会存在任何访问冲突。That means there is no access violation when model, model.Movie, or model.Movie[0] are null or empty. 对 Lambda 表达式求值时(例如,使用 @Html.DisplayFor(modelItem => item.Title)),将求得该模型的属性值。When the lambda expression is evaluated (for example, with @Html.DisplayFor(modelItem => item.Title)), the model's property values are evaluated.

@model 指令The @model directive

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@model 指令指定传递给 Razor 页面的模型类型。The @model directive specifies the type of the model passed to the Razor Page. 在前面的示例中,@model 行使 PageModel 派生的类可用于 Razor 页面。In the preceding example, the @model line makes the PageModel-derived class available to the Razor Page. 在页面上的 @Html.DisplayNameFor@Html.DisplayFor HTML 帮助程序中使用该模型。The model is used in the @Html.DisplayNameFor and @Html.DisplayFor HTML Helpers on the page.

布局页The layout page

选择菜单链接(“RazorPagesMovie”、“主页”和“隐私”)。Select the menu links (RazorPagesMovie, Home, and Privacy). 每页显示相同的菜单布局。Each page shows the same menu layout. 菜单布局是在 Pages/Shared/_Layout.cshtml 文件中实现。The menu layout is implemented in the Pages/Shared/_Layout.cshtml file. 打开 Pages/Shared/_Layout.cshtml 文件。Open the Pages/Shared/_Layout.cshtml file.

布局模板使你能够在一个位置指定网站的 HTML 容器布局,然后将它应用到网站中的多个页面。Layout templates allow you to specify the HTML container layout of your site in one place and then apply it across multiple pages in your site. 查找 @RenderBody() 行。Find the @RenderBody() line. RenderBody 是显示所创建的全部页面专用视图的占位符,已包装在布局页中。RenderBody is a placeholder where all the page-specific views you create show up, wrapped in the layout page. 例如,如果选择“隐私”链接,Pages/Privacy.cshtml 视图在 RenderBody 方法中呈现。For example, if you select the Privacy link, the Pages/Privacy.cshtml view is rendered inside the RenderBody method.

ViewData 和布局ViewData and layout

考虑来自 Pages/Movies/Index.cshtml 文件中的以下代码:Consider the following code from the Pages/Movies/Index.cshtml file:

@page
@model RazorPagesMovie.Pages.Movies.IndexModel

@{
    ViewData["Title"] = "Index";
}

前面突出显示的代码是 Razor 转换为 C# 的一个示例。The preceding highlighted code is an example of Razor transitioning into C#. {} 字符括住 C# 代码块。The { and } characters enclose a block of C# code.

PageModel 基类具有 ViewData 字典属性,可用于添加要传递到某个视图的数据。The PageModel base class has a ViewData dictionary property that can be used to add data that you want to pass to a View. 可以使用键/值模式将对象添加到 ViewData 字典。You add objects into the ViewData dictionary using a key/value pattern. 在前面的示例中,“Title”属性被添加到 ViewData 字典。In the preceding sample, the "Title" property is added to the ViewData dictionary.

“Title”属性用于 Pages/Shared/_Layout.cshtml 文件。The "Title" property is used in the Pages/Shared/_Layout.cshtml file. 以下标记显示 _Layout.cshtml 文件的前几行。The following markup shows the first few lines of the _Layout.cshtml file.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - RazorPagesMovie</title>

    @*Markup removed for brevity.*@

@*Markup removed for brevity.*@ 是不会出现在布局文件中的 Razor 注释。The line @*Markup removed for brevity.*@ is a Razor comment which doesn't appear in your layout file. 与 HTML 注释不同 (<!-- -->),Razor 注释不会发送到客户端。Unlike HTML comments (<!-- -->), Razor comments are not sent to the client.

更新布局Update the layout

更改 Pages/Shared/_Layout.cshtml 文件中的 <title> 元素以显示 Movie 而不是 RazorPagesMovie。Change the <title> element in the Pages/Shared/_Layout.cshtml file to display Movie rather than RazorPagesMovie.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Movie</title>

在 Pages/Shared/_Layout.cshtml 文件中,查找以下定位点元素。Find the following anchor element in the Pages/Shared/_Layout.cshtml file.

<a class="navbar-brand" asp-area="" asp-page="/Index">RazorPagesMovie</a>

将前面的元素替换为以下标记。Replace the preceding element with the following markup.

<a class="navbar-brand" asp-page="/Movies/Index">RpMovie</a>

前面的定位点元素是一个标记帮助程序The preceding anchor element is a Tag Helper. 此处它是定位点标记帮助程序In this case, it's the Anchor Tag Helper. asp-page="/Movies/Index" 标记帮助程序属性和值可以创建指向 /Movies/Index Razor 页面的链接。The asp-page="/Movies/Index" Tag Helper attribute and value creates a link to the /Movies/Index Razor Page. asp-area 属性值为空,因此在链接中未使用区域。The asp-area attribute value is empty, so the area isn't used in the link. 有关详细信息,请参阅区域See Areas for more information.

保存所做的更改,并通过单击“RpMovie”链接测试应用。Save your changes, and test the app by clicking on the RpMovie link. 如果遇到任何问题,请参阅 GitHub 中的 _Layout.cshtml 文件。See the _Layout.cshtml file in GitHub if you have any problems.

测试其他链接(“主页”、“RpMovie”、“创建”、“编辑”和“删除”)。Test the other links (Home, RpMovie, Create, Edit, and Delete). 每个页面都设置有标题,可以在浏览器选项卡中看到标题。将某个页面加入书签时,标题用于该书签。Each page sets the title, which you can see in the browser tab. When you bookmark a page, the title is used for the bookmark.

备注

可能无法在 Price 字段中输入十进制逗号。You may not be able to enter decimal commas in the Price field. 若要使 jQuery 验证支持使用逗号(“,”)表示小数点的的非英语区域设置,以及支持非美国英语日期格式,必须执行使应用全球化的步骤。To support jQuery validation for non-English locales that use a comma (",") for a decimal point, and non US-English date formats, you must take steps to globalize your app. 有关添加十进制逗号的说明,请参阅 GitHub 问题 4076This GitHub issue 4076 for instructions on adding decimal comma.

在 Pages/_ViewStart.cshtml 文件中设置 Layout 属性:The Layout property is set in the Pages/_ViewStart.cshtml file:

@{
    Layout = "_Layout";
}

前面的标记针对所有 Razor 文件将布局文件设置为 Pages 文件夹下的 Pages/Shared/_Layout.cshtml。The preceding markup sets the layout file to Pages/Shared/_Layout.cshtml for all Razor files under the Pages folder. 请参阅布局了解详细信息。See Layout for more information.

“创建”页面模型The Create page model

检查 Pages/Movies/Create.cshtml.cs 页面模型:Examine the Pages/Movies/Create.cshtml.cs page model:

// Unused usings removed.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesMovie.Models;
using System;
using System.Threading.Tasks;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        private readonly RazorPagesMovie.Models.RazorPagesMovieContext _context;

        public CreateModel(RazorPagesMovie.Models.RazorPagesMovieContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        public Movie Movie { get; set; }

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Movie.Add(Movie);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }
}

OnGet 方法初始化页面所需的任何状态。The OnGet method initializes any state needed for the page. “创建”页没有任何要初始化的状态,因此返回 PageThe Create page doesn't have any state to initialize, so Page is returned. 教程的后面部分将介绍 OnGet 方法初始化状态。Later in the tutorial you see OnGet method initialize state. Page 方法创建用于呈现 Create.cshtml 页的 PageResult 对象。The Page method creates a PageResult object that renders the Create.cshtml page.

Movie 属性使用 [BindProperty] 特性来选择加入模型绑定The Movie property uses the [BindProperty] attribute to opt-in to model binding. 当“创建”表单发布表单值时,ASP.NET Core 运行时将发布的值绑定到 Movie 模型。When the Create form posts the form values, the ASP.NET Core runtime binds the posted values to the Movie model.

当页面发布表单数据时,运行 OnPostAsync 方法:The OnPostAsync method is run when the page posts form data:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movie.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

如果不存在任何模型错误,将重新显示表单,以及发布的任何表单数据。If there are any model errors, the form is redisplayed, along with any form data posted. 在发布表单前,可以在客户端捕获到大部分模型错误。Most model errors can be caught on the client-side before the form is posted. 模型错误的一个示例是,发布的日期字段值无法转换为日期。An example of a model error is posting a value for the date field that cannot be converted to a date. 本教程后面讨论了客户端验证和模型验证。Client-side validation and model validation are discussed later in the tutorial.

如果不存在模型错误,将保存数据,并且浏览器会重定向到索引页。If there are no model errors, the data is saved, and the browser is redirected to the Index page.

创建 Razor 页面The Create Razor Page

检查 Pages/Movies/Create.cshtml Razor 页面文件:Examine the Pages/Movies/Create.cshtml Razor Page file:

@page
@model RazorPagesMovie.Pages.Movies.CreateModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Movie.Title" class="control-label"></label>
                <input asp-for="Movie.Title" class="form-control" />
                <span asp-validation-for="Movie.Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.ReleaseDate" class="control-label"></label>
                <input asp-for="Movie.ReleaseDate" class="form-control" />
                <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Genre" class="control-label"></label>
                <input asp-for="Movie.Genre" class="form-control" />
                <span asp-validation-for="Movie.Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Movie.Price" class="control-label"></label>
                <input asp-for="Movie.Price" class="form-control" />
                <span asp-validation-for="Movie.Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Visual Studio 以用于标记帮助程序的特殊加粗字体显示 <form method="post"> 标记:Visual Studio displays the <form method="post"> tag in a distinctive bold font used for Tag Helpers:

Create.cshtml 页的 VS17 视图

<form method="post"> 元素是一个表单标记帮助程序The <form method="post"> element is a Form Tag Helper. 表单标记帮助程序会自动包含防伪令牌The Form Tag Helper automatically includes an antiforgery token.

基架引擎在模型中为每个字段(ID 除外)创建 Razor 标记,如下所示:The scaffolding engine creates Razor markup for each field in the model (except the ID) similar to the following:

<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
    <label asp-for="Movie.Title" class="control-label"></label>
    <input asp-for="Movie.Title" class="form-control" />
    <span asp-validation-for="Movie.Title" class="text-danger"></span>
</div>

验证标记帮助程序<div asp-validation-summary<span asp-validation-for)显示验证错误。The Validation Tag Helpers (<div asp-validation-summary and <span asp-validation-for) display validation errors. 本系列后面的部分将更详细地讨论有关验证的信息。Validation is covered in more detail later in this series.

标签标记帮助程序 (<label asp-for="Movie.Title" class="control-label"></label>) 生成标签描述和 Title 属性的 for 特性。The Label Tag Helper (<label asp-for="Movie.Title" class="control-label"></label>) generates the label caption and for attribute for the Title property.

输入标记帮助程序 (<input asp-for="Movie.Title" class="form-control">) 使用 DataAnnotations 属性并在客户端生成 jQuery 验证所需的 HTML 属性。The Input Tag Helper (<input asp-for="Movie.Title" class="form-control">) uses the DataAnnotations attributes and produces HTML attributes needed for jQuery Validation on the client-side.

其他资源Additional resources