ASP.NET Core 中的 Razor Pages 介绍Introduction to Razor Pages in ASP.NET Core

作者:Rick AndersonRyan NowakBy Rick Anderson and Ryan Nowak

通过 Razor Pages 对基于页面的场景编码比使用控制器和视图更轻松、更高效。Razor Pages can make coding page-focused scenarios easier and more productive than using controllers and views.

若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门If you're looking for a tutorial that uses the Model-View-Controller approach, see Get started with ASP.NET Core MVC.

本文档介绍 Razor Pages。This document provides an introduction to Razor Pages. 它并不是分步教程。It's not a step by step tutorial. 如果认为某些部分过于复杂,请参阅 Razor Pages 入门If you find some of the sections too advanced, see Get started with Razor Pages. 有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介For an overview of ASP.NET Core, see the Introduction to ASP.NET Core.

先决条件Prerequisites

创建 Razor Pages 项目Create a Razor Pages project

请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。See Get started with Razor Pages for detailed instructions on how to create a Razor Pages project.

Razor PagesRazor Pages

Startup.cs 中已启用 Razor 页面:Razor Pages is enabled in Startup.cs:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

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

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

请考虑一个基本页面:Consider a basic page:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

前面的代码与具有控制器和视图的 ASP.NET Core 应用中使用的 Razor 视图文件非常相似。The preceding code looks a lot like a Razor view file used in an ASP.NET Core app with controllers and views. 不同之处在于 @page 指令。What makes it different is the @page directive. @page 使文件转换为一个 MVC 操作 ,这意味着它将直接处理请求,而无需通过控制器处理。@page makes the file into an MVC action - which means that it handles requests directly, without going through a controller. @page 必须是页面上的第一个 Razor 指令。@page must be the first Razor directive on a page. @page 会影响其他的 Razor 构造。@page affects the behavior of other Razor constructs. Razor Pages 文件名有 .cshtml 后缀。Razor Pages file names have a .cshtml suffix.

将在以下两个文件中显示使用 PageModel 类的类似页面。A similar page, using a PageModel class, is shown in the following two files. Pages/Index2.cshtml 文件:The Pages/Index2.cshtml file:

@page
@using RazorPagesIntro.Pages
@model Index2Model

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Pages/Index2.cshtml.cs 页面模型:The Pages/Index2.cshtml.cs page model:

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System;

namespace RazorPagesIntro.Pages
{
    public class Index2Model : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

按照惯例,PageModel 类文件的名称与追加 .cs 的 Razor Page 文件名称相同。By convention, the PageModel class file has the same name as the Razor Page file with .cs appended. 例如,前面的 Razor Page 的名称为 Pages/Index2.cshtml。For example, the previous Razor Page is Pages/Index2.cshtml. 包含 PageModel 类的文件的名称为 Pages/Index2.cshtml.cs。The file containing the PageModel class is named Pages/Index2.cshtml.cs.

页面的 URL 路径的关联由页面在文件系统中的位置决定。The associations of URL paths to pages are determined by the page's location in the file system. 下表显示了 Razor Page 路径及匹配的 URL:The following table shows a Razor Page path and the matching URL:

文件名和路径File name and path 匹配的 URLmatching URL
/Pages/Index.cshtml/Pages/Index.cshtml //Index/ or /Index
/Pages/Contact.cshtml/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml/Pages/Store/Index.cshtml /Store/Store/Index/Store or /Store/Index

注意:Notes:

  • 默认情况下,运行时在“Pages”文件夹中查找 Razor Pages 文件。The runtime looks for Razor Pages files in the Pages folder by default.
  • URL 未包含页面时,Index 为默认页面。Index is the default page when a URL doesn't include a page.

编写基本窗体Write a basic form

由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。Razor Pages is designed to make common patterns used with web browsers easy to implement when building an app. 模型绑定标记帮助程序和 HTML 帮助程序均只可用于 Razor Page 类中定义的属性。Model binding, Tag Helpers, and HTML helpers all just work with the properties defined in a Razor Page class. 请参考为 Contact 模型实现的基本的“联系我们”窗体页面:Consider a page that implements a basic "contact us" form for the Contact model:

在本文档中的示例中,DbContextStartup.cs 文件中进行初始化。For the samples in this document, the DbContext is initialized in the Startup.cs file.

内存中数据库需要 Microsoft.EntityFrameworkCore.InMemory NuGet 包。The in memory database requires the Microsoft.EntityFrameworkCore.InMemory NuGet package.

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<CustomerDbContext>(options =>
                      options.UseInMemoryDatabase("name"));
    services.AddRazorPages();
}

数据模型:The data model:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

数据库上下文:The db context:

using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Models;

namespace RazorPagesContacts.Data
{
    public class CustomerDbContext : DbContext
    {
        public CustomerDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Pages/Create.cshtml 视图文件:The Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Pages/Create.cshtml.cs 页面模型:The Pages/Create.cshtml.cs page model:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using RazorPagesContacts.Models;
using System.Threading.Tasks;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateModel : PageModel
    {
        private readonly CustomerDbContext _context;

        public CreateModel(CustomerDbContext context)
        {
            _context = context;
        }

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

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

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

            _context.Customers.Add(Customer);
            await _context.SaveChangesAsync();

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

按照惯例,PageModel 类命名为 <PageName>Model并且它与页面位于同一个命名空间中。By convention, the PageModel class is called <PageName>Model and is in the same namespace as the page.

使用 PageModel 类,可以将页面的逻辑与其展示分离开来。The PageModel class allows separation of the logic of a page from its presentation. 它定义了页面处理程序,用于处理发送到页面的请求和用于呈现页面的数据。It defines page handlers for requests sent to the page and the data used to render the page. 这种隔离可实现:This separation allows:

页面包含 OnPostAsync 处理程序方法,它在 POST 请求上运行(当用户发布窗体时)。The page has an OnPostAsync handler method, which runs on POST requests (when a user posts the form). 可以添加任何 HTTP 谓词的处理程序方法。Handler methods for any HTTP verb can be added. 最常见的处理程序是:The most common handlers are:

  • OnGet,用于初始化页面所需的状态。OnGet to initialize state needed for the page. 在上面的代码中,OnGet 方法显示 CreateModel.cshtml Razor Page。In the preceding code, the OnGet method displays the CreateModel.cshtml Razor Page.
  • OnPost,用于处理窗体提交。OnPost to handle form submissions.

Async 命名后缀为可选,但是按照惯例通常会将它用于异步函数。The Async naming suffix is optional but is often used by convention for asynchronous functions. 前面的代码通常用于 Razor Pages。The preceding code is typical for Razor Pages.

如果你熟悉使用控制器和视图的 ASP.NET 应用:If you're familiar with ASP.NET apps using controllers and views:

  • 前面示例中的 OnPostAsync 代码类似于典型的控制器代码。The OnPostAsync code in the preceding example looks similar to typical controller code.
  • 大多数 MVC 基元(例如模型绑定验证和操作结果)通过控制器和通过 Razor Pages 的运作方式相同。Most of the MVC primitives like model binding, validation, and action results work the same with Controllers and Razor Pages.

之前的 OnPostAsync 方法:The previous OnPostAsync method:

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

    _context.Customers.Add(Customer);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

OnPostAsync 的基本流:The basic flow of OnPostAsync:

检查验证错误。Check for validation errors.

  • 如果没有错误,则保存数据并重定向。If there are no errors, save the data and redirect.
  • 如果有错误,则再次显示页面并附带验证消息。If there are errors, show the page again with validation messages. 很多情况下,都会在客户端上检测到验证错误,并且从不将它们提交到服务器。In many cases, validation errors would be detected on the client, and never submitted to the server.

Pages/Create.cshtml 视图文件:The Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

Pages/Create.cshtml 中呈现的 HTML:The rendered HTML from Pages/Create.cshtml:

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input type="text" data-val="true"
           data-val-length="The field Name must be a string with a maximum length of 10."
           data-val-length-max="10" data-val-required="The Name field is required."
           id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
    <input type="submit" />
    <input name="__RequestVerificationToken" type="hidden"
           value="<Antiforgery token here>" />
</form>

在前面的代码中,发布窗体:In the previous code, posting the form:

  • 对于有效数据:With valid data:

    • OnPostAsync 处理程序方法调用 RedirectToPage 帮助程序方法。The OnPostAsync handler method calls the RedirectToPage helper method. RedirectToPage 返回 RedirectToPageResult 的实例。RedirectToPage returns an instance of RedirectToPageResult. RedirectToPage:RedirectToPage:

      • 是操作结果。Is an action result.
      • 类似于 RedirectToActionRedirectToRoute(用于控制器和视图)。Is similar to RedirectToAction or RedirectToRoute (used in controllers and views).
      • 针对页面自定义。Is customized for pages. 在前面的示例中,它将重定向到根索引页 (/Index)。In the preceding sample, it redirects to the root Index page (/Index). 页面 URL 生成部分中详细介绍了 RedirectToPageRedirectToPage is detailed in the URL generation for Pages section.
  • 对于传递给服务器的验证错误:With validation errors that are passed to the server:

    • OnPostAsync 处理程序方法调用 Page 帮助程序方法。The OnPostAsync handler method calls the Page helper method. Page 返回 PageResult 的实例。Page returns an instance of PageResult. 返回 Page 的过程与控制器中的操作返回 View 的过程相似。Returning Page is similar to how actions in controllers return View. PageResult 是处理程序方法的默认返回类型。PageResult is the default return type for a handler method. 返回 void 的处理程序方法将显示页面。A handler method that returns void renders the page.
    • 在前面的示例中,在 ModelState.IsValid 中的值结果不返回 false 的情况下发布窗体。In the preceding example, posting the form with no value results in ModelState.IsValid returning false. 在此示例中,客户端上不显示任何验证错误。In this sample, no validation errors are displayed on the client. 本文档的后面将介绍验证错误处理。Validation error handing is covered later in this document.
    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();
    
        return RedirectToPage("./Index");
    }
    
  • 对于客户端验证检测到的验证错误:With validation errors detected by client side validation:

    • 数据不会发布到服务器。Data is not posted to the server.
    • 本文档的后面将介绍客户端验证。Client-side validation is explained later in this document.

Customer 属性使用 [BindProperty] 特性来选择加入模型绑定:The Customer property uses [BindProperty] attribute to opt in to model binding:

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

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

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

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

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

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

[BindProperty] 不应 用于包含不应由客户端更改的属性的模型。[BindProperty] should not be used on models containing properties that should not be changed by the client. 有关详细信息,请参阅过度发布For more information, see Overposting.

Razor Pages 只绑定带有非 GET 谓词的属性。Razor Pages, by default, bind properties only with non-GET verbs. 如果绑定到属性,则无需通过编写代码将 HTTP 数据转换为模型类型。Binding to properties removes the need to writing code to convert HTTP data to the model type. 绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">) 来减少代码,并接受输入。Binding reduces code by using the same property to render form fields (<input asp-for="Customer.Name">) and accept the input.

警告

出于安全原因,必须选择绑定 GET 请求数据以对模型属性进行分页。For security reasons, you must opt in to binding GET request data to page model properties. 请在将用户输入映射到属性前对其进行验证。Verify user input before mapping it to properties. 当处理依赖查询字符串或路由值的方案时,选择加入 GET 绑定非常有用。Opting into GET binding is useful when addressing scenarios that rely on query string or route values.

若要将属性绑定在 GET 请求上,请将 [BindProperty] 特性的 SupportsGet 属性设置为 trueTo bind a property on GET requests, set the [BindProperty] attribute's SupportsGet property to true:

[BindProperty(SupportsGet = true)]

有关详细信息,请参阅 ASP.NET Core Community Standup:Bind on GET discussion (YouTube)(绑定 GET 讨论)。For more information, see ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

查看 Pages/Create.cshtml 视图文件:Reviewing the Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>
  • 在前面的代码中,输入标记帮助程序 <input asp-for="Customer.Name" /> 将 HTML <input> 元素绑定到 Customer.Name 模型表达式。In the preceding code, the input tag helper <input asp-for="Customer.Name" /> binds the HTML <input> element to the Customer.Name model expression.
  • 使用 @addTagHelper 提供标记帮助程序。@addTagHelper makes Tag Helpers available.

主页The home page

Index.cshtml 是主页:Index.cshtml is the home page:

@page
@model RazorPagesContacts.Pages.Customers.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts home page</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customer)
            {
                <tr>
                    <td> @contact.Id  </td>
                    <td>@contact.Name</td>
                    <td>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                        <button type="submit" asp-page-handler="delete"
                                asp-route-id="@contact.Id">delete
                        </button>
                    </td>
                </tr>
            }
        </tbody>
    </table>
    <a asp-page="Create">Create New</a>
</form>

关联的 PageModel 类 (Index.cshtml.cs):The associated PageModel class (Index.cshtml.cs):

public class IndexModel : PageModel
{
    private readonly CustomerDbContext _context;

    public IndexModel(CustomerDbContext context)
    {
        _context = context;
    }

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

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

    public async Task<IActionResult> OnPostDeleteAsync(int id)
    {
        var contact = await _context.Customers.FindAsync(id);

        if (contact != null)
        {
            _context.Customers.Remove(contact);
            await _context.SaveChangesAsync();
        }

        return RedirectToPage();
    }
}

Index.cshtml 文件包含以下标记:The Index.cshtml file contains the following markup:

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |

<a /a> 定位点标记帮助程序使用 asp-route-{value} 属性生成“编辑”页面的链接。The <a /a> Anchor Tag Helper used the asp-route-{value} attribute to generate a link to the Edit page. 此链接包含路由数据及联系人 ID。The link contains route data with the contact ID. 例如 https://localhost:5001/Edit/1For example, https://localhost:5001/Edit/1. 标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files.

Index.cshtml 文件包含用于为每个客户联系人创建删除按钮的标记:The Index.cshtml file contains markup to create a delete button for each customer contact:

<button type="submit" asp-page-handler="delete"
        asp-route-id="@contact.Id">delete

呈现的 HTML:The rendered HTML:

<button type="submit" formaction="/Customers?id=1&amp;handler=delete">delete</button>

删除按钮采用 HTML 呈现,其 formaction 包括参数:When the delete button is rendered in HTML, its formaction includes parameters for:

  • asp-route-id 属性指定的客户联系人 ID。The customer contact ID, specified by the asp-route-id attribute.
  • asp-page-handler 属性指定的 handlerThe handler, specified by the asp-page-handler attribute.

选中按钮时,向服务器发送窗体 POST 请求。When the button is selected, a form POST request is sent to the server. 按照惯例,根据方案 OnPost[handler]Async 基于 handler 参数的值来选择处理程序方法的名称。By convention, the name of the handler method is selected based on the value of the handler parameter according to the scheme OnPost[handler]Async.

因为本示例中 handlerdelete,因此 OnPostDeleteAsync 处理程序方法用于处理 POST 请求。Because the handler is delete in this example, the OnPostDeleteAsync handler method is used to process the POST request. 如果 asp-page-handler 设置为其他值(如 remove),则选择名称为 OnPostRemoveAsync 的处理程序方法。If the asp-page-handler is set to a different value, such as remove, a handler method with the name OnPostRemoveAsync is selected.

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _context.Customers.FindAsync(id);

    if (contact != null)
    {
        _context.Customers.Remove(contact);
        await _context.SaveChangesAsync();
    }

    return RedirectToPage();
}

OnPostDeleteAsync 方法:The OnPostDeleteAsync method:

  • 获取来自查询字符串的 idGets the id from the query string.
  • 使用 FindAsync 查询客户联系人的数据库。Queries the database for the customer contact with FindAsync.
  • 如果找到客户联系人,则会将其删除,并更新数据库。If the customer contact is found, it's removed and the database is updated.
  • 调用 RedirectToPage,重定向到根索引页 (/Index)。Calls RedirectToPage to redirect to the root Index page (/Index).

Edit.cshtml 文件The Edit.cshtml file

@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers


<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
    <div asp-validation-summary="All"></div>
    <input asp-for="Customer.Id" type="hidden" />
    <div>
        <label asp-for="Customer.Name"></label>
        <div>
            <input asp-for="Customer.Name" />
            <span asp-validation-for="Customer.Name"></span>
        </div>
    </div>

    <div>
        <button type="submit">Save</button>
    </div>
</form>

第一行包含 @page "{id:int}" 指令。The first line contains the @page "{id:int}" directive. 路由约束 "{id:int}" 告诉页面接受包含 int 路由数据的页面请求。The routing constraint"{id:int}" tells the page to accept requests to the page that contain int route data. 如果页面请求未包含可转换为 int 的路由数据,则运行时返回 HTTP 404(未找到)错误。If a request to the page doesn't contain route data that can be converted to an int, the runtime returns an HTTP 404 (not found) error. 若要使 ID 可选,请将 ? 追加到路由约束:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Edit.cshtml.cs 文件:The Edit.cshtml.cs file:

public class EditModel : PageModel
{
    private readonly CustomerDbContext _context;

    public EditModel(CustomerDbContext context)
    {
        _context = context;
    }

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

    public async Task<IActionResult> OnGetAsync(int id)
    {
        Customer = await _context.Customers.FindAsync(id);

        if (Customer == null)
        {
            return RedirectToPage("./Index");
        }

        return Page();
    }

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

        _context.Attach(Customer).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            throw new Exception($"Customer {Customer.Id} not found!");
        }

        return RedirectToPage("./Index");
    }

}

验证Validation

验证规则:Validation rules:

  • 在模型类中以声明方式指定。Are declaratively specified in the model class.
  • 在应用中的所有位置强制执行。Are enforced everywhere in the app.

System.ComponentModel.DataAnnotations 命名空间提供一组内置验证特性,可通过声明方式应用于类或属性。The System.ComponentModel.DataAnnotations namespace provides a set of built-in validation attributes that are applied declaratively to a class or property. DataAnnotations 还包含 [DataType] 等格式特性,有助于格式设置但不提供任何验证。DataAnnotations also contains formatting attributes like [DataType] that help with formatting and don't provide any validation.

请考虑 Customer 模型:Consider the Customer model:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Models
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(10)]
        public string Name { get; set; }
    }
}

使用以下 Create.cshtml 视图文件:Using the following Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

前面的代码:The preceding code:

  • 包括 jQuery 和 jQuery 验证脚本。Includes jQuery and jQuery validation scripts.

  • 使用 <div /><span /> 标记帮助程序以实现:Uses the <div /> and <span /> Tag Helpers to enable:

    • 客户端验证。Client-side validation.
    • 验证错误呈现。Validation error rendering.
  • 则会生成以下 HTML:Generates the following HTML:

    <p>Enter a customer name:</p>
    
    <form method="post">
        Name:
        <input type="text" data-val="true"
               data-val-length="The field Name must be a string with a maximum length of 10."
               data-val-length-max="10" data-val-required="The Name field is required."
               id="Customer_Name" maxlength="10" name="Customer.Name" value="" />
        <input type="submit" />
        <input name="__RequestVerificationToken" type="hidden"
               value="<Antiforgery token here>" />
    </form>
    
    <script src="/lib/jquery/dist/jquery.js"></script>
    <script src="/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
    

如果在不使用名称值的情况下发布“创建”窗体,则将显示错误消息“名称字段是必需的”。Posting the Create form without a name value displays the error message "The Name field is required." 窗体上。on the form. 如果客户端上已启用 JavaScript,浏览器会显示错误,而不会发布到服务器。If JavaScript is enabled on the client, the browser displays the error without posting to the server.

[StringLength(10)] 特性在呈现的 HTML 上生成 data-val-length-max="10"The [StringLength(10)] attribute generates data-val-length-max="10" on the rendered HTML. data-val-length-max 阻止浏览器输入超过指定最大长度的内容。data-val-length-max prevents browsers from entering more than the maximum length specified. 如果使用 Fiddler 等工具来编辑和重播文章:If a tool such as Fiddler is used to edit and replay the post:

  • 对于长度超过 10 的名称。With the name longer than 10.
  • 错误消息“字段名称必须是最大长度为 10 的字符串。”The error message "The field Name must be a string with a maximum length of 10." 将返回。is returned.

考虑下列 Movie 模型:Consider the following Movie model:

public class Movie
{
    public int ID { get; set; }

    [StringLength(60, MinimumLength = 3)]
    [Required]
    public string Title { get; set; }

    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }

    [Range(1, 100)]
    [DataType(DataType.Currency)]
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
    [Required]
    [StringLength(30)]
    public string Genre { get; set; }

    [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
    [StringLength(5)]
    [Required]
    public string Rating { get; set; }
}

验证特性指定要对应用这些特性的模型属性强制执行的行为:The validation attributes specify behavior to enforce on the model properties they're applied to:

  • RequiredMinimumLength 特性表示属性必须有值,但用户可输入空格来满足此验证。The Required and MinimumLength attributes indicate that a property must have a value, but nothing prevents a user from entering white space to satisfy this validation.

  • RegularExpression 特性用于限制可输入的字符。The RegularExpression attribute is used to limit what characters can be input. 在上述代码中,即“Genre”(分类):In the preceding code, "Genre":

    • 只能使用字母。Must only use letters.
    • 第一个字母必须为大写。The first letter is required to be uppercase. 不允许使用空格、数字和特殊字符。White space, numbers, and special characters are not allowed.
  • RegularExpression“Rating”(分级):The RegularExpression "Rating":

    • 要求第一个字符为大写字母。Requires that the first character be an uppercase letter.
    • 允许在后续空格中使用特殊字符和数字。Allows special characters and numbers in subsequent spaces. “PG-13”对“分级”有效,但对于“分类”无效。"PG-13" is valid for a rating, but fails for a "Genre".
  • Range 特性将值限制在指定范围内。The Range attribute constrains a value to within a specified range.

  • StringLength 特性可以设置字符串属性的最大长度,以及可选的最小长度。The StringLength attribute sets the maximum length of a string property, and optionally its minimum length.

  • 从本质上来说,需要值类型(如 decimalintfloatDateTime),但不需要 [Required] 特性。Value types (such as decimal, int, float, DateTime) are inherently required and don't need the [Required] attribute.

Movie 模型的“创建”页面显示无效值错误:The Create page for the Movie model shows displays errors with invalid values:

带有多个 jQuery 客户端验证错误的电影视图表单

有关详情,请参阅:For more information, see:

使用 OnGet 处理程序回退来处理 HEAD 请求Handle HEAD requests with an OnGet handler fallback

HEAD 请求可检索特定资源的标头。HEAD requests allow retrieving the headers for a specific resource. GET 请求不同,HEAD 请求不返回响应正文。Unlike GET requests, HEAD requests don't return a response body.

通常,针对 HEAD 请求创建和调用 OnHead 处理程序:Ordinarily, an OnHead handler is created and called for HEAD requests:

public void OnHead()
{
    HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}

如果未定义 OnHead 处理程序,则 Razor Pages 会回退到调用 OnGet 处理程序。Razor Pages falls back to calling the OnGet handler if no OnHead handler is defined.

XSRF/CSRF 和 Razor PagesXSRF/CSRF and Razor Pages

Razor Pages 由 防伪造验证保护。Razor Pages are protected by Antiforgery validation. FormTagHelper 将防伪造令牌注入 HTML 窗体元素。The FormTagHelper injects antiforgery tokens into HTML form elements.

将布局、分区、模板和标记帮助程序用于 Razor PagesUsing Layouts, partials, templates, and Tag Helpers with Razor Pages

页面可使用 Razor 视图引擎的所有功能。Pages work with all the capabilities of the Razor view engine. 布局、分区、模板、标记帮助程序、_ViewStart.cshtml 和 _ViewImports.cshtml 的工作方式与它们在传统的 Razor 视图中的工作方式相同。Layouts, partials, templates, Tag Helpers, _ViewStart.cshtml, and _ViewImports.cshtml work in the same way they do for conventional Razor views.

让我们使用其中的一些功能来整理此页面。Let's declutter this page by taking advantage of some of those capabilities.

向 Pages/Shared/_Layout.cshtml 添加布局页面Add a layout page to Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <title>RP Sample</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
</head>
<body>
    <a asp-page="/Index">Home</a>
    <a asp-page="/Customers/Create">Create</a>
    <a asp-page="/Customers/Index">Customers</a> <br />

    @RenderBody()
    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</body>
</html>

布局The Layout:

  • 控制每个页面的布局(页面选择退出布局时除外)。Controls the layout of each page (unless the page opts out of layout).
  • 导入 HTML 结构,例如 JavaScript 和样式表。Imports HTML structures such as JavaScript and stylesheets.
  • 调用 @RenderBody() 时,呈现 Razor Page 的内容。The contents of the Razor page are rendered where @RenderBody() is called.

有关详细信息,请参阅布局页面For more information, see layout page.

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

@{
    Layout = "_Layout";
}

布局位于“页面/共享”文件夹中。The layout is in the Pages/Shared folder. 页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。Pages look for other views (layouts, templates, partials) hierarchically, starting in the same folder as the current page. 可以从“Pages”文件夹下的任意 Razor 页面使用“Pages/Shared”文件夹中的布局。A layout in the Pages/Shared folder can be used from any Razor page under the Pages folder.

布局文件应位于 Pages/Shared 文件夹中。The layout file should go in the Pages/Shared folder.

建议不要将布局文件放在“视图/共享”文件夹中。We recommend you not put the layout file in the Views/Shared folder. 视图/共享 是一种 MVC 视图模式。Views/Shared is an MVC views pattern. Razor Pages 旨在依赖文件夹层次结构,而非路径约定。Razor Pages are meant to rely on folder hierarchy, not path conventions.

Razor Page 中的视图搜索包含“Pages”文件夹。View search from a Razor Page includes the Pages folder. 用于 MVC 控制器和传统 Razor 视图的布局、模板和分区可正常运行。The layouts, templates, and partials used with MVC controllers and conventional Razor views just work.

添加 Pages/_ViewImports.cshtml 文件:Add a Pages/_ViewImports.cshtml file:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

本教程的后续部分中将介绍 @namespace@namespace is explained later in the tutorial. @addTagHelper 指令将内置标记帮助程序引入“页面”文件夹中的所有页面。The @addTagHelper directive brings in the built-in Tag Helpers to all the pages in the Pages folder.

页面上设置的 @namespace 指令:The @namespace directive set on a page:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

@namespace 指令将为页面设置命名空间。The @namespace directive sets the namespace for the page. @model 指令无需包含命名空间。The @model directive doesn't need to include the namespace.

_ViewImports.cshtml 中包含 @namespace 指令后,指定的命名空间将为在导入 @namespace 指令的页面中生成的命名空间提供前缀。When the @namespace directive is contained in _ViewImports.cshtml, the specified namespace supplies the prefix for the generated namespace in the Page that imports the @namespace directive. 生成的命名空间的剩余部分(后缀部分)是包含 _ViewImports.cshtml 的文件夹与包含页面的文件夹之间以点分隔的相对路径。The rest of the generated namespace (the suffix portion) is the dot-separated relative path between the folder containing _ViewImports.cshtml and the folder containing the page.

例如,PageModel 类 Pages/Customers/Edit.cshtml.cs 显式设置命名空间:For example, the PageModel class Pages/Customers/Edit.cshtml.cs explicitly sets the namespace:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

Pages/_ViewImports.cshtml 文件设置以下命名空间:The Pages/_ViewImports.cshtml file sets the following namespace:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

为 Pages/Customers/Edit.cshtml Razor Page 生成的命名空间与 PageModel 类相同。The generated namespace for the Pages/Customers/Edit.cshtml Razor Page is the same as the PageModel class.

@namespace 也适用于传统 Razor 视图。@namespace also works with conventional Razor views.

考虑 Pages/Create.cshtml 视图文件:Consider the Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.Customers.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<p>Validation: customer name:</p>

<form method="post">
    <div asp-validation-summary="ModelOnly"></div>
    <span asp-validation-for="Customer.Name"></span>
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>

包含 _ViewImports.cshtml 的已更新的 Pages/Create.cshtml 视图文件和前面的布局文件 :The updated Pages/Create.cshtml view file with _ViewImports.cshtml and the preceding layout file:

@page
@model CreateModel

<p>Enter a customer name:</p>

<form method="post">
    Name:
    <input asp-for="Customer.Name" />
    <input type="submit" />
</form>

在前面的代码中,_ViewImports.cshtml 导入了命名空间和标记帮助程序。In the preceding code, the _ViewImports.cshtml imported the namespace and Tag Helpers. 布局文件导入了 JavaScript 文件。The layout file imported the JavaScript files.

Razor Pages 初学者项目包含 Pages/_ValidationScriptsPartial.cshtml,它与客户端验证联合。The Razor Pages starter project contains the Pages/_ValidationScriptsPartial.cshtml, which hooks up client-side validation.

有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图For more information on partial views, see ASP.NET Core 中的分部视图.

页面的 URL 生成URL generation for Pages

之前显示的 Create 页面使用 RedirectToPageThe Create page, shown previously, uses RedirectToPage:

public class CreateModel : PageModel
{
    private readonly CustomerDbContext _context;

    public CreateModel(CustomerDbContext context)
    {
        _context = context;
    }

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

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

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

        _context.Customers.Add(Customer);
        await _context.SaveChangesAsync();

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

应用具有以下文件/文件夹结构:The app has the following file/folder structure:

  • /Pages/Pages

    • Index.cshtmlIndex.cshtml

    • Privacy.cshtmlPrivacy.cshtml

    • /Customers/Customers

      • Create.cshtmlCreate.cshtml
      • Edit.cshtmlEdit.cshtml
      • Index.cshtmlIndex.cshtml

成功后,Pages/Customers/Create.cshtml and Pages/Customers/Edit.cshtml 页面将重定向到 Pages/Customers/Index.cshtml 。The Pages/Customers/Create.cshtml and Pages/Customers/Edit.cshtml pages redirect to Pages/Customers/Index.cshtml after success. 字符串 ./Index 是用于访问前一页的相对页名称。The string ./Index is a relative page name used to access the preceding page. 它用于生成 Pages/Customers/Index.cshtml 页面的 URL。It is used to generate URLs to the Pages/Customers/Index.cshtml page. 例如:For example:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")

绝对页名称 /Index 用于生成 Pages/Index. cshtml 页面的 URL。The absolute page name /Index is used to generate URLs to the Pages/Index.cshtml page. 例如:For example:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")

页面名称是从根“/Pages”文件夹到页面的路径(包含前导 /,例如 /Index)。The page name is the path to the page from the root /Pages folder including a leading / (for example, /Index). 与硬编码 URL 相比,前面的 URL 生成示例提供了改进的选项和功能。The preceding URL generation samples offer enhanced options and functional capabilities over hard-coding a URL. URL 生成使用路由,并且可以根据目标路径定义路由的方式生成参数并对参数编码。URL generation uses routing and can generate and encode parameters according to how the route is defined in the destination path.

页面的 URL 生成支持相对名称。URL generation for pages supports relative names. 下表显示了 Pages/Customers/Create.cshtml 中不同的 RedirectToPage 参数选择的索引页。The following table shows which Index page is selected using different RedirectToPage parameters in Pages/Customers/Create.cshtml.

RedirectToPage(x)RedirectToPage(x) 页面Page
RedirectToPage("/Index")RedirectToPage("/Index") Pages/IndexPages/Index
RedirectToPage("./Index");RedirectToPage("./Index"); Pages/Customers/IndexPages/Customers/Index
RedirectToPage("../Index")RedirectToPage("../Index") Pages/IndexPages/Index
RedirectToPage("Index")RedirectToPage("Index") Pages/Customers/IndexPages/Customers/Index

RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是相对名称。RedirectToPage("Index"), RedirectToPage("./Index"), and RedirectToPage("../Index") are relative names. 结合RedirectToPage 参数与当前页的路径来计算目标页面的名称。The RedirectToPage parameter is combined with the path of the current page to compute the name of the destination page.

构建结构复杂的站点时,相对名称链接很有用。Relative name linking is useful when building sites with a complex structure. 如果使用相对名称链接文件夹中的页面:When relative names are used to link between pages in a folder:

  • 重命名文件夹不会破坏相对链接。Renaming a folder doesn't break the relative links.
  • 链接不会中断,因为它们不包含文件夹名称。Links are not broken because they don't include the folder name.

若要重定向到不同区域中的页面,请指定区域:To redirect to a page in a different Area, specify the area:

RedirectToPage("/Index", new { area = "Services" });

有关详细信息,请参阅 ASP.NET Core 中的区域ASP.NET Core 中的 Razor Pages 路由和应用约定For more information, see ASP.NET Core 中的区域 and ASP.NET Core 中的 Razor Pages 路由和应用约定.

ViewData 特性ViewData attribute

可以通过 ViewDataAttribute 将数据传递到页面。Data can be passed to a page with ViewDataAttribute. 具有 [ViewData] 特性的属性从 ViewDataDictionary 保存和加载值。Properties with the [ViewData] attribute have their values stored and loaded from the ViewDataDictionary.

在下面的示例中,AboutModel[ViewData] 特性应用于 Title 属性:In the following example, the AboutModel applies the [ViewData] attribute to the Title property:

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

在“关于”页面中,以模型属性的形式访问 Title 属性:In the About page, access the Title property as a model property:

<h1>@Model.Title</h1>

在布局中,从 ViewData 字典读取标题:In the layout, the title is read from the ViewData dictionary:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempDataTempData

ASP.NET Core 公开 TempDataASP.NET Core exposes the TempData. 此属性存储未读取的数据。This property stores data until it's read. KeepPeek 方法可用于检查数据,而不执行删除。The Keep and Peek methods can be used to examine the data without deletion. TempData 在多个请求需要数据的情况下对重定向很有用。TempData is useful for redirection, when data is needed for more than a single request.

下面的代码使用 TempData 设置 Message 的值:The following code sets the value of Message using TempData:

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

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

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

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Pages/Customers/Index.cshtml 文件中的以下标记使用 TempData 显示 Message 的值。The following markup in the Pages/Customers/Index.cshtml file displays the value of Message using TempData.

<h3>Msg: @Model.Message</h3>

Pages/Customers/Index.cshtml.cs 页面模型将 [TempData] 属性应用到 Message 属性。The Pages/Customers/Index.cshtml.cs page model applies the [TempData] attribute to the Message property.

[TempData]
public string Message { get; set; }

有关详细信息,请参阅 TempDataFor more information, see TempData.

针对一个页面的多个处理程序Multiple handlers per page

以下页面使用 asp-page-handler 标记帮助程序为两个处理程序生成标记:The following page generates markup for two handlers using the asp-page-handler Tag Helper:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper 提交到不同的 URL。The form in the preceding example has two submit buttons, each using the FormActionTagHelper to submit to a different URL. asp-page-handlerasp-page 的配套属性。The asp-page-handler attribute is a companion to asp-page. asp-page-handler 生成提交到页面定义的各个处理程序方法的 URL。asp-page-handler generates URLs that submit to each of the handler methods defined by a page. 未指定 asp-page,因为示例已链接到当前页面。asp-page isn't specified because the sample is linking to the current page.

页面模型:The page model:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

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

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

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

前面的代码使用已命名处理程序方法。The preceding code uses named handler methods. 已命名处理程序方法通过采用名称中 On<HTTP Verb> 之后及 Async 之前的文本(如果有)创建。Named handler methods are created by taking the text in the name after On<HTTP Verb> and before Async (if present). 在前面的示例中,页面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。In the preceding example, the page methods are OnPostJoinListAsync and OnPostJoinListUCAsync. 删除 OnPost 和 Async 后,处理程序名称为 JoinListJoinListUCWith OnPost and Async removed, the handler names are JoinList and JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

使用前面的代码时,提交到 OnPostJoinListAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUsing the preceding code, the URL path that submits to OnPostJoinListAsync is https://localhost:5001/Customers/CreateFATH?handler=JoinList. 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUCThe URL path that submits to OnPostJoinListUCAsync is https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

自定义路由Custom routes

使用 @page 指令,执行以下操作:Use the @page directive to:

  • 指定页面的自定义路由。Specify a custom route to a page. 例如,使用 @page "/Some/Other/Path" 将“关于”页面的路由设置为 /Some/Other/PathFor example, the route to the About page can be set to /Some/Other/Path with @page "/Some/Other/Path".
  • 将段追加到页面的默认路由。Append segments to a page's default route. 例如,可使用 @page "item" 将“item”段添加到页面的默认路由。For example, an "item" segment can be added to a page's default route with @page "item".
  • 将参数追加到页面的默认路由。Append parameters to a page's default route. 例如,@page "{id}" 页面需要 ID 参数 idFor example, an ID parameter, id, can be required for a page with @page "{id}".

支持开头处以波形符 (~) 指定的相对于根目录的路径。A root-relative path designated by a tilde (~) at the beginning of the path is supported. 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。For example, @page "~/Some/Other/Path" is the same as @page "/Some/Other/Path".

如果你不喜欢 URL 中的查询字符串 ?handler=JoinList,请更改路由,将处理程序名称放在 URL 的路径部分。If you don't like the query string ?handler=JoinList in the URL, change the route to put the handler name in the path portion of the URL. 可以通过在 @page 指令后面添加使用双引号括起来的路由模板来自定义路由。The route can be customized by adding a route template enclosed in double quotes after the @page directive.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

使用前面的代码时,提交到 OnPostJoinListAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUsing the preceding code, the URL path that submits to OnPostJoinListAsync is https://localhost:5001/Customers/CreateFATH/JoinList. 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUCThe URL path that submits to OnPostJoinListUCAsync is https://localhost:5001/Customers/CreateFATH/JoinListUC.

handler 前面的 ? 表示路由参数为可选。The ? following handler means the route parameter is optional.

高级配置和设置Advanced configuration and settings

大多数应用不需要以下部分中的配置和设置。The configuration and settings in following sections is not required by most apps.

要配置高级选项,请使用 AddRazorPages 重载,该重载配置 RazorPagesOptionsTo configure advanced options, use the AddRazorPages overload that configures RazorPagesOptions:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
    {
        options.RootDirectory = "/MyPages";
        options.Conventions.AuthorizeFolder("/MyPages/Admin");
    });
}

使用 RazorPagesOptions 设置页面的根目录,或者为页面添加应用程序模型约定。Use the RazorPagesOptions to set the root directory for pages, or add application model conventions for pages. 有关约定的详细信息,请参阅 Razor Pages 授权约定.For more information on conventions, see Razor Pages authorization conventions.

若要预编译视图,请参阅 Razor 视图编译To precompile views, see Razor view compilation.

指定 Razor Pages 位于内容根目录中Specify that Razor Pages are at the content root

默认情况下,Razor Pages 位于 /Pages 目录的根位置。By default, Razor Pages are rooted in the /Pages directory. 添加 WithRazorPagesAtContentRoot 以指定 Razor Pages 位于应用的内容根 (ContentRootPath):Add WithRazorPagesAtContentRoot to specify that your Razor Pages are at the content root (ContentRootPath) of the app:

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesAtContentRoot();
}

指定 Razor Pages 位于自定义根目录中Specify that Razor Pages are at a custom root directory

添加 WithRazorPagesRoot,以指定 Razor Pages 位于应用中自定义根目录位置(提供相对路径):Add WithRazorPagesRoot to specify that Razor Pages are at a custom root directory in the app (provide a relative path):

public void ConfigureServices(IServiceCollection services)
{            
    services.AddRazorPages(options =>
        {
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        })
        .WithRazorPagesRoot("/path/to/razor/pages");
}

其他资源Additional resources

作者:Rick AndersonRyan NowakBy Rick Anderson and Ryan Nowak

Razor Pages 是 ASP.NET Core MVC 的一个新特性,它可以使基于页面的编码方式更简单高效。Razor Pages is a new aspect of ASP.NET Core MVC that makes coding page-focused scenarios easier and more productive.

若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门If you're looking for a tutorial that uses the Model-View-Controller approach, see Get started with ASP.NET Core MVC.

本文档介绍 Razor Pages。This document provides an introduction to Razor Pages. 它并不是分步教程。It's not a step by step tutorial. 如果认为某些部分过于复杂,请参阅 Razor Pages 入门If you find some of the sections too advanced, see Get started with Razor Pages. 有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介For an overview of ASP.NET Core, see the Introduction to ASP.NET Core.

先决条件Prerequisites

警告

如果使用 Visual Studio 2017,请参阅 dotnet/sdk 问题 #3124,以了解无法与 Visual Studio 一起使用的 .NET Core SDK 版本的信息。If you use Visual Studio 2017, see dotnet/sdk issue #3124 for information about .NET Core SDK versions that don't work with Visual Studio.

创建 Razor Pages 项目Create a Razor Pages project

请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。See Get started with Razor Pages for detailed instructions on how to create a Razor Pages project.

Razor PagesRazor Pages

Startup.cs 中已启用 Razor 页面:Razor Pages is enabled in Startup.cs:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Includes support for Razor Pages and controllers.
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
    }
}

请考虑一个基本页面:Consider a basic page:

@page

<h1>Hello, world!</h1>
<h2>The time on the server is @DateTime.Now</h2>

前面的代码与具有控制器和视图的 ASP.NET Core 应用中使用的 Razor 视图文件非常相似。The preceding code looks a lot like a Razor view file used in an ASP.NET Core app with controllers and views. 不同之处在于 @page 指令。What makes it different is the @page directive. @page 使文件转换为一个 MVC 操作 ,这意味着它将直接处理请求,而无需通过控制器处理。@page makes the file into an MVC action - which means that it handles requests directly, without going through a controller. @page 必须是页面上的第一个 Razor 指令。@page must be the first Razor directive on a page. @page 会影响其他 Razor 构造的行为。@page affects the behavior of other Razor constructs.

将在以下两个文件中显示使用 PageModel 类的类似页面。A similar page, using a PageModel class, is shown in the following two files. Pages/Index2.cshtml 文件:The Pages/Index2.cshtml file:

@page
@using RazorPagesIntro.Pages
@model IndexModel2

<h2>Separate page model</h2>
<p>
    @Model.Message
</p>

Pages/Index2.cshtml.cs 页面模型:The Pages/Index2.cshtml.cs page model:

using Microsoft.AspNetCore.Mvc.RazorPages;
using System;

namespace RazorPagesIntro.Pages
{
    public class IndexModel2 : PageModel
    {
        public string Message { get; private set; } = "PageModel in C#";

        public void OnGet()
        {
            Message += $" Server time is { DateTime.Now }";
        }
    }
}

按照惯例,PageModel 类文件的名称与追加 .cs 的 Razor Page 文件名称相同。By convention, the PageModel class file has the same name as the Razor Page file with .cs appended. 例如,前面的 Razor Page 的名称为 Pages/Index2.cshtml。For example, the previous Razor Page is Pages/Index2.cshtml. 包含 PageModel 类的文件的名称为 Pages/Index2.cshtml.cs。The file containing the PageModel class is named Pages/Index2.cshtml.cs.

页面的 URL 路径的关联由页面在文件系统中的位置决定。The associations of URL paths to pages are determined by the page's location in the file system. 下表显示了 Razor Page 路径及匹配的 URL:The following table shows a Razor Page path and the matching URL:

文件名和路径File name and path 匹配的 URLmatching URL
/Pages/Index.cshtml/Pages/Index.cshtml //Index/ or /Index
/Pages/Contact.cshtml/Pages/Contact.cshtml /Contact
/Pages/Store/Contact.cshtml/Pages/Store/Contact.cshtml /Store/Contact
/Pages/Store/Index.cshtml/Pages/Store/Index.cshtml /Store/Store/Index/Store or /Store/Index

注意:Notes:

  • 默认情况下,运行时在“Pages”文件夹中查找 Razor Pages 文件。The runtime looks for Razor Pages files in the Pages folder by default.
  • URL 未包含页面时,Index 为默认页面。Index is the default page when a URL doesn't include a page.

编写基本窗体Write a basic form

由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。Razor Pages is designed to make common patterns used with web browsers easy to implement when building an app. 模型绑定标记帮助程序和 HTML 帮助程序均只可用于 Razor Page 类中定义的属性。Model binding, Tag Helpers, and HTML helpers all just work with the properties defined in a Razor Page class. 请参考为 Contact 模型实现的基本的“联系我们”窗体页面:Consider a page that implements a basic "contact us" form for the Contact model:

在本文档中的示例中,DbContextStartup.cs 文件中进行初始化。For the samples in this document, the DbContext is initialized in the Startup.cs file.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesContacts.Data;

namespace RazorPagesContacts
{
    public class Startup
    {
        public IHostingEnvironment HostingEnvironment { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<AppDbContext>(options =>
                              options.UseInMemoryDatabase("name"));
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

数据模型:The data model:

using System.ComponentModel.DataAnnotations;

namespace RazorPagesContacts.Data
{
    public class Customer
    {
        public int Id { get; set; }

        [Required, StringLength(100)]
        public string Name { get; set; }
    }
}

数据库上下文:The db context:

using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Data
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions options)
            : base(options)
        {
        }

        public DbSet<Customer> Customers { get; set; }
    }
}

Pages/Create.cshtml 视图文件:The Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

Pages/Create.cshtml.cs 页面模型:The Pages/Create.cshtml.cs page model:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
    public class CreateModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateModel(AppDbContext db)
        {
            _db = db;
        }

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

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

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }
    }
}

按照惯例,PageModel 类命名为 <PageName>Model并且它与页面位于同一个命名空间中。By convention, the PageModel class is called <PageName>Model and is in the same namespace as the page.

使用 PageModel 类,可以将页面的逻辑与其展示分离开来。The PageModel class allows separation of the logic of a page from its presentation. 它定义了页面处理程序,用于处理发送到页面的请求和用于呈现页面的数据。It defines page handlers for requests sent to the page and the data used to render the page. 这种隔离可实现:This separation allows:

页面包含 OnPostAsync 处理程序方法,它在 POST 请求上运行(当用户发布窗体时)。The page has an OnPostAsync handler method, which runs on POST requests (when a user posts the form). 可以为任何 HTTP 谓词添加处理程序方法。You can add handler methods for any HTTP verb. 最常见的处理程序是:The most common handlers are:

  • OnGet,用于初始化页面所需的状态。OnGet to initialize state needed for the page. OnGet 示例。OnGet sample.
  • OnPost,用于处理窗体提交。OnPost to handle form submissions.

Async 命名后缀为可选,但是按照惯例通常会将它用于异步函数。The Async naming suffix is optional but is often used by convention for asynchronous functions. 前面的代码通常用于 Razor Pages。The preceding code is typical for Razor Pages.

如果你熟悉使用控制器和视图的 ASP.NET 应用:If you're familiar with ASP.NET apps using controllers and views:

  • 前面示例中的 OnPostAsync 代码类似于典型的控制器代码。The OnPostAsync code in the preceding example looks similar to typical controller code.
  • 多数 MVC 基元(例如模型绑定验证验证和操作结果)都是共享的。Most of the MVC primitives like model binding, validation, Validation, and action results are shared.

之前的 OnPostAsync 方法:The previous OnPostAsync method:

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

    _db.Customers.Add(Customer);
    await _db.SaveChangesAsync();
    return RedirectToPage("/Index");
}

OnPostAsync 的基本流:The basic flow of OnPostAsync:

检查验证错误。Check for validation errors.

  • 如果没有错误,则保存数据并重定向。If there are no errors, save the data and redirect.
  • 如果有错误,则再次显示页面并附带验证消息。If there are errors, show the page again with validation messages. 客户端验证与传统的 ASP.NET Core MVC 应用程序相同。Client-side validation is identical to traditional ASP.NET Core MVC applications. 很多情况下,都会在客户端上检测到验证错误,并且从不将它们提交到服务器。In many cases, validation errors would be detected on the client, and never submitted to the server.

成功输入数据后,OnPostAsync 处理程序方法调用 RedirectToPage 帮助程序方法来返回 RedirectToPageResult 的实例。When the data is entered successfully, the OnPostAsync handler method calls the RedirectToPage helper method to return an instance of RedirectToPageResult. RedirectToPage 是新的操作结果,类似于 RedirectToActionRedirectToRoute,但是已针对页面进行自定义。RedirectToPage is a new action result, similar to RedirectToAction or RedirectToRoute, but customized for pages. 在前面的示例中,它将重定向到根索引页 (/Index)。In the preceding sample, it redirects to the root Index page (/Index). 页面 URL 生成部分中详细介绍了 RedirectToPageRedirectToPage is detailed in the URL generation for Pages section.

提交的窗体存在(已传递到服务器的)验证错误时,OnPostAsync 处理程序方法调用 Page 帮助程序方法。When the submitted form has validation errors (that are passed to the server), theOnPostAsync handler method calls the Page helper method. Page 返回 PageResult 的实例。Page returns an instance of PageResult. 返回 Page 的过程与控制器中的操作返回 View 的过程相似。Returning Page is similar to how actions in controllers return View. PageResult 是处理程序方法的默认返回类型。PageResult is the default return type for a handler method. 返回 void 的处理程序方法将显示页面。A handler method that returns void renders the page.

Customer 属性使用 [BindProperty] 特性来选择加入模型绑定。The Customer property uses [BindProperty] attribute to opt in to model binding.

public class CreateModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateModel(AppDbContext db)
    {
        _db = db;
    }

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

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

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        return RedirectToPage("/Index");
    }
}

Razor Pages 只绑定带有非 GET 谓词的属性。Razor Pages, by default, bind properties only with non-GET verbs. 绑定属性可以减少需要编写的代码量。Binding to properties can reduce the amount of code you have to write. 绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">) 来减少代码,并接受输入。Binding reduces code by using the same property to render form fields (<input asp-for="Customer.Name">) and accept the input.

警告

出于安全原因,必须选择绑定 GET 请求数据以对模型属性进行分页。For security reasons, you must opt in to binding GET request data to page model properties. 请在将用户输入映射到属性前对其进行验证。Verify user input before mapping it to properties. 当处理依赖查询字符串或路由值的方案时,选择加入 GET 绑定非常有用。Opting into GET binding is useful when addressing scenarios that rely on query string or route values.

若要将属性绑定在 GET 请求上,请将 [BindProperty] 特性的 SupportsGet 属性设置为 trueTo bind a property on GET requests, set the [BindProperty] attribute's SupportsGet property to true:

[BindProperty(SupportsGet = true)]

有关详细信息,请参阅 ASP.NET Core Community Standup:Bind on GET discussion (YouTube)(绑定 GET 讨论)。For more information, see ASP.NET Core Community Standup: Bind on GET discussion (YouTube).

主页 (Index.cshtml):The home page (Index.cshtml):

@page
@model RazorPagesContacts.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Contacts</h1>
<form method="post">
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var contact in Model.Customers)
            {
                <tr>
                    <td>@contact.Id</td>
                    <td>@contact.Name</td>
                    <td>
                        <a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
                        <button type="submit" asp-page-handler="delete" 
                                asp-route-id="@contact.Id">delete</button>
                    </td>
                </tr>
            }
        </tbody>
    </table>

    <a asp-page="./Create">Create</a>
</form>

关联的 PageModel 类 (Index.cshtml.cs):The associated PageModel class (Index.cshtml.cs):

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;

namespace RazorPagesContacts.Pages
{
    public class IndexModel : PageModel
    {
        private readonly AppDbContext _db;

        public IndexModel(AppDbContext db)
        {
            _db = db;
        }

        public IList<Customer> Customers { get; private set; }

        public async Task OnGetAsync()
        {
            Customers = await _db.Customers.AsNoTracking().ToListAsync();
        }

        public async Task<IActionResult> OnPostDeleteAsync(int id)
        {
            var contact = await _db.Customers.FindAsync(id);

            if (contact != null)
            {
                _db.Customers.Remove(contact);
                await _db.SaveChangesAsync();
            }

            return RedirectToPage();
        }
    }
}

Index.cshtml 文件包含以下标记来创建每个联系人项的编辑链接:The Index.cshtml file contains the following markup to create an edit link for each contact:

<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>

<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> 定位点标记帮助程序使用 asp-route-{value} 属性生成“编辑”页面的链接。The <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> Anchor Tag Helper used the asp-route-{value} attribute to generate a link to the Edit page. 此链接包含路由数据及联系人 ID。The link contains route data with the contact ID. 例如 https://localhost:5001/Edit/1For example, https://localhost:5001/Edit/1. 标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。Tag Helpers enable server-side code to participate in creating and rendering HTML elements in Razor files. 标记帮助程序由 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 启动Tag Helpers are enabled by @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Pages/Edit.cshtml 文件:The Pages/Edit.cshtml file:

@page "{id:int}"
@model RazorPagesContacts.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@{
    ViewData["Title"] = "Edit Customer";
}

<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
    <div asp-validation-summary="All"></div>
    <input asp-for="Customer.Id" type="hidden" />
    <div>
        <label asp-for="Customer.Name"></label>
        <div>
            <input asp-for="Customer.Name" />
            <span asp-validation-for="Customer.Name" ></span>
        </div>
    </div>
 
    <div>
        <button type="submit">Save</button>
    </div>
</form>

第一行包含 @page "{id:int}" 指令。The first line contains the @page "{id:int}" directive. 路由约束 "{id:int}" 告诉页面接受包含 int 路由数据的页面请求。The routing constraint"{id:int}" tells the page to accept requests to the page that contain int route data. 如果页面请求未包含可转换为 int 的路由数据,则运行时返回 HTTP 404(未找到)错误。If a request to the page doesn't contain route data that can be converted to an int, the runtime returns an HTTP 404 (not found) error. 若要使 ID 可选,请将 ? 追加到路由约束:To make the ID optional, append ? to the route constraint:

@page "{id:int?}"

Pages/Edit.cshtml.cs 文件:The Pages/Edit.cshtml.cs file:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

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

        public async Task<IActionResult> OnGetAsync(int id)
        {
            Customer = await _db.Customers.FindAsync(id);

            if (Customer == null)
            {
                return RedirectToPage("/Index");
            }

            return Page();
        }

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

            _db.Attach(Customer).State = EntityState.Modified;

            try
            {
                await _db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                throw new Exception($"Customer {Customer.Id} not found!");
            }

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

Index.cshtml 文件还包含用于为每个客户联系人创建删除按钮的标记:The Index.cshtml file also contains markup to create a delete button for each customer contact:

<button type="submit" asp-page-handler="delete" 
        asp-route-id="@contact.Id">delete</button>

删除按钮采用 HTML 呈现,其 formaction 包括参数:When the delete button is rendered in HTML, its formaction includes parameters for:

  • asp-route-id 属性指定的客户联系人 ID。The customer contact ID specified by the asp-route-id attribute.
  • asp-page-handler 属性指定的 handlerThe handler specified by the asp-page-handler attribute.

下面是呈现的删除按钮的示例,其中客户联系人 ID 为 1Here is an example of a rendered delete button with a customer contact ID of 1:

<button type="submit" formaction="/?id=1&amp;handler=delete">delete</button>

选中按钮时,向服务器发送窗体 POST 请求。When the button is selected, a form POST request is sent to the server. 按照惯例,根据方案 OnPost[handler]Async 基于 handler 参数的值来选择处理程序方法的名称。By convention, the name of the handler method is selected based on the value of the handler parameter according to the scheme OnPost[handler]Async.

因为本示例中 handlerdelete,因此 OnPostDeleteAsync 处理程序方法用于处理 POST 请求。Because the handler is delete in this example, the OnPostDeleteAsync handler method is used to process the POST request. 如果 asp-page-handler 设置为其他值(如 remove),则选择名称为 OnPostRemoveAsync 的处理程序方法。If the asp-page-handler is set to a different value, such as remove, a handler method with the name OnPostRemoveAsync is selected. 以下代码显示了 OnPostDeleteAsync 处理程序:The following code shows the OnPostDeleteAsync handler:

public async Task<IActionResult> OnPostDeleteAsync(int id)
{
    var contact = await _db.Customers.FindAsync(id);

    if (contact != null)
    {
        _db.Customers.Remove(contact);
        await _db.SaveChangesAsync();
    }

    return RedirectToPage();
}

OnPostDeleteAsync 方法:The OnPostDeleteAsync method:

  • 接受来自查询字符串的 idAccepts the id from the query string. 如果 Index.cshtml 页面指令包含路由约束 "{id:int?}",则 id 来自路由数据。If the Index.cshtml page directive contained routing constraint "{id:int?}", id would come from route data. id 的路由数据在 https://localhost:5001/Customers/2 等 URI 中指定。The route data for id is specified in the URI such as https://localhost:5001/Customers/2.
  • 使用 FindAsync 查询客户联系人的数据库。Queries the database for the customer contact with FindAsync.
  • 如果找到客户联系人,则从客户联系人列表将其删除。If the customer contact is found, they're removed from the list of customer contacts. 数据库将更新。The database is updated.
  • 调用 RedirectToPage,重定向到根索引页 (/Index)。Calls RedirectToPage to redirect to the root Index page (/Index).

按需标记页面属性Mark page properties as required

PageModel 上的属性可通过 Required 特性进行标记:Properties on a PageModel can be marked with the Required attribute:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;

namespace RazorPagesMovie.Pages.Movies
{
    public class CreateModel : PageModel
    {
        public IActionResult OnGet()
        {
            return Page();
        }

        [BindProperty]
        [Required(ErrorMessage = "Color is required")]
        public string Color { get; set; }

        public IActionResult OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            // Process color.

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

有关详细信息,请参阅模型验证For more information, see Model validation.

使用 OnGet 处理程序回退来处理 HEAD 请求Handle HEAD requests with an OnGet handler fallback

HEAD 请求可检索特定资源的标头。HEAD requests allow you to retrieve the headers for a specific resource. GET 请求不同,HEAD 请求不返回响应正文。Unlike GET requests, HEAD requests don't return a response body.

通常,针对 HEAD 请求创建和调用 OnHead 处理程序:Ordinarily, an OnHead handler is created and called for HEAD requests:

public void OnHead()
{
    HttpContext.Response.Headers.Add("HandledBy", "Handled by OnHead!");
}

在 ASP.NET Core 2.1 或更高版本中,如果未定义 OnHead 处理程序,则 Razor Pages 会回退到调用 OnGet 处理程序。In ASP.NET Core 2.1 or later, Razor Pages falls back to calling the OnGet handler if no OnHead handler is defined. 此行为是通过在 Startup.ConfigureServices 中调用 SetCompatibilityVersion 而启用的:This behavior is enabled by the call to SetCompatibilityVersion in Startup.ConfigureServices:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

默认模板在 ASP.NET Core 2.1 和 2.2 中生成 SetCompatibilityVersion 调用。The default templates generate the SetCompatibilityVersion call in ASP.NET Core 2.1 and 2.2. SetCompatibilityVersion 有效地将 Razor Pages 选项 AllowMappingHeadRequestsToGetHandler 设置为 trueSetCompatibilityVersion effectively sets the Razor Pages option AllowMappingHeadRequestsToGetHandler to true.

可显式选择使用特定行为,而不是通过 SetCompatibilityVersion 选择使用所有行为。Rather than opting in to all behaviors with SetCompatibilityVersion, you can explicitly opt in to specific behaviors. 通过下列代码,可选择允许将 HEAD 请求映射到 OnGet 处理程序:The following code opts in to allowing HEAD requests to be mapped to the OnGet handler:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        options.AllowMappingHeadRequestsToGetHandler = true;
    });

XSRF/CSRF 和 Razor PagesXSRF/CSRF and Razor Pages

无需为防伪验证编写任何代码。You don't have to write any code for antiforgery validation. Razor Pages 自动将防伪标记生成过程和验证过程包含在内。Antiforgery token generation and validation are automatically included in Razor Pages.

将布局、分区、模板和标记帮助程序用于 Razor PagesUsing Layouts, partials, templates, and Tag Helpers with Razor Pages

页面可使用 Razor 视图引擎的所有功能。Pages work with all the capabilities of the Razor view engine. 布局、分区、模板、标记帮助程序、_ViewStart.cshtml 和 _ViewImports.cshtml 的工作方式与它们在传统的 Razor 视图中的工作方式相同。Layouts, partials, templates, Tag Helpers, _ViewStart.cshtml, _ViewImports.cshtml work in the same way they do for conventional Razor views.

让我们使用其中的一些功能来整理此页面。Let's declutter this page by taking advantage of some of those capabilities.

向 Pages/Shared/_Layout.cshtml 添加布局页面Add a layout page to Pages/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head> 
    <title>Razor Pages Sample</title>      
</head>
<body>    
   <a asp-page="/Index">Home</a>
    @RenderBody()  
    <a asp-page="/Customers/Create">Create</a> <br />
</body>
</html>

布局The Layout:

  • 控制每个页面的布局(页面选择退出布局时除外)。Controls the layout of each page (unless the page opts out of layout).
  • 导入 HTML 结构,例如 JavaScript 和样式表。Imports HTML structures such as JavaScript and stylesheets.

请参阅布局页面了解详细信息。See layout page for more information.

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

@{
    Layout = "_Layout";
}

布局位于“页面/共享”文件夹中。The layout is in the Pages/Shared folder. 页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。Pages look for other views (layouts, templates, partials) hierarchically, starting in the same folder as the current page. 可以从“Pages”文件夹下的任意 Razor 页面使用“Pages/Shared”文件夹中的布局。A layout in the Pages/Shared folder can be used from any Razor page under the Pages folder.

布局文件应位于 Pages/Shared 文件夹中。The layout file should go in the Pages/Shared folder.

建议不要将布局文件放在“视图/共享”文件夹中。We recommend you not put the layout file in the Views/Shared folder. 视图/共享 是一种 MVC 视图模式。Views/Shared is an MVC views pattern. Razor Pages 旨在依赖文件夹层次结构,而非路径约定。Razor Pages are meant to rely on folder hierarchy, not path conventions.

Razor Page 中的视图搜索包含“Pages”文件夹。View search from a Razor Page includes the Pages folder. 要用于 MVC 控制器和传统 Razor 视图的布局、模板和分区可正常运行。The layouts, templates, and partials you're using with MVC controllers and conventional Razor views just work.

添加 Pages/_ViewImports.cshtml 文件:Add a Pages/_ViewImports.cshtml file:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

本教程的后续部分中将介绍 @namespace@namespace is explained later in the tutorial. @addTagHelper 指令将内置标记帮助程序引入“页面”文件夹中的所有页面。The @addTagHelper directive brings in the built-in Tag Helpers to all the pages in the Pages folder.

页面上显式使用 @namespace 指令后:When the @namespace directive is used explicitly on a page:

@page
@namespace RazorPagesIntro.Pages.Customers

@model NameSpaceModel

<h2>Name space</h2>
<p>
    @Model.Message
</p>

此指令将为页面设置命名空间。The directive sets the namespace for the page. @model 指令无需包含命名空间。The @model directive doesn't need to include the namespace.

_ViewImports.cshtml 中包含 @namespace 指令后,指定的命名空间将为在导入 @namespace 指令的页面中生成的命名空间提供前缀。When the @namespace directive is contained in _ViewImports.cshtml, the specified namespace supplies the prefix for the generated namespace in the Page that imports the @namespace directive. 生成的命名空间的剩余部分(后缀部分)是包含 _ViewImports.cshtml 的文件夹与包含页面的文件夹之间以点分隔的相对路径。The rest of the generated namespace (the suffix portion) is the dot-separated relative path between the folder containing _ViewImports.cshtml and the folder containing the page.

例如,PageModel 类 Pages/Customers/Edit.cshtml.cs 显式设置命名空间:For example, the PageModel class Pages/Customers/Edit.cshtml.cs explicitly sets the namespace:

namespace RazorPagesContacts.Pages
{
    public class EditModel : PageModel
    {
        private readonly AppDbContext _db;

        public EditModel(AppDbContext db)
        {
            _db = db;
        }

        // Code removed for brevity.

Pages/_ViewImports.cshtml 文件设置以下命名空间:The Pages/_ViewImports.cshtml file sets the following namespace:

@namespace RazorPagesContacts.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

为 Pages/Customers/Edit.cshtml Razor Page 生成的命名空间与 PageModel 类相同。The generated namespace for the Pages/Customers/Edit.cshtml Razor Page is the same as the PageModel class.

@namespace 也适用于传统 Razor 视图。@namespace also works with conventional Razor views.

原始的 Pages/Create.cshtml 视图文件:The original Pages/Create.cshtml view file:

@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

更新后的 Pages/Create.cshtml 视图文件:The updated Pages/Create.cshtml view file:

@page
@model CreateModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" />
    </form>
</body>
</html>

Razor Pages 初学者项目包含 Pages/_ValidationScriptsPartial.cshtml,它与客户端验证联合。The Razor Pages starter project contains the Pages/_ValidationScriptsPartial.cshtml, which hooks up client-side validation.

有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图For more information on partial views, see ASP.NET Core 中的分部视图.

页面的 URL 生成URL generation for Pages

之前显示的 Create 页面使用 RedirectToPageThe Create page, shown previously, uses RedirectToPage:

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

    _db.Customers.Add(Customer);
    await _db.SaveChangesAsync();
    return RedirectToPage("/Index");
}

应用具有以下文件/文件夹结构:The app has the following file/folder structure:

  • /Pages/Pages

    • Index.cshtmlIndex.cshtml

    • /Customers/Customers

      • Create.cshtmlCreate.cshtml
      • Edit.cshtmlEdit.cshtml
      • Index.cshtmlIndex.cshtml

成功后,Pages/Customers/Create.cshtml 和 Pages/Customers/Edit.cshtml 页面将重定向到 Pages/Index.cshtml。The Pages/Customers/Create.cshtml and Pages/Customers/Edit.cshtml pages redirect to Pages/Index.cshtml after success. 字符串 /Index 是用于访问上一页的 URI 的组成部分。The string /Index is part of the URI to access the preceding page. 可以使用字符串 /Index 生成 Pages/Index.cshtml 页面的 URI。The string /Index can be used to generate URIs to the Pages/Index.cshtml page. 例如:For example:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">My Index Page</a>
  • RedirectToPage("/Index")

页面名称是从根“/Pages”文件夹到页面的路径(包含前导 /,例如 /Index)。The page name is the path to the page from the root /Pages folder including a leading / (for example, /Index). 与硬编码 URL 相比,前面的 URL 生成示例提供了改进的选项和功能。The preceding URL generation samples offer enhanced options and functional capabilities over hardcoding a URL. URL 生成使用路由,并且可以根据目标路径定义路由的方式生成参数并对参数编码。URL generation uses routing and can generate and encode parameters according to how the route is defined in the destination path.

页面的 URL 生成支持相对名称。URL generation for pages supports relative names. 下表显示了 Pages/Customers/Create.cshtml 中不同的 RedirectToPage 参数选择的索引页:The following table shows which Index page is selected with different RedirectToPage parameters from Pages/Customers/Create.cshtml:

RedirectToPage(x)RedirectToPage(x) 页面Page
RedirectToPage("/Index")RedirectToPage("/Index") Pages/IndexPages/Index
RedirectToPage("./Index");RedirectToPage("./Index"); Pages/Customers/IndexPages/Customers/Index
RedirectToPage("../Index")RedirectToPage("../Index") Pages/IndexPages/Index
RedirectToPage("Index")RedirectToPage("Index") Pages/Customers/IndexPages/Customers/Index

RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是相对名称。RedirectToPage("Index"), RedirectToPage("./Index"), and RedirectToPage("../Index") are relative names. 结合RedirectToPage 参数与当前页的路径来计算目标页面的名称。The RedirectToPage parameter is combined with the path of the current page to compute the name of the destination page.

构建结构复杂的站点时,相对名称链接很有用。Relative name linking is useful when building sites with a complex structure. 如果使用相对名称链接文件夹中的页面,则可以重命名该文件夹。If you use relative names to link between pages in a folder, you can rename that folder. 所有链接仍然有效(因为这些链接未包含此文件夹名称)。All the links still work (because they didn't include the folder name).

若要重定向到不同区域中的页面,请指定区域:To redirect to a page in a different Area, specify the area:

RedirectToPage("/Index", new { area = "Services" });

有关详细信息,请参阅 ASP.NET Core 中的区域For more information, see ASP.NET Core 中的区域.

ViewData 特性ViewData attribute

可以通过 ViewDataAttribute 将数据传递到页面。Data can be passed to a page with ViewDataAttribute. 控制器或 Razor Pages 模型上使用 [ViewData] 属性的属性将其值存储在 ViewDataDictionary 中并从此处进行加载。Properties on controllers or Razor Page models with the [ViewData] attribute have their values stored and loaded from the ViewDataDictionary.

在下面的示例中,AboutModel 包含使用 [ViewData] 标记的 Title 属性。In the following example, the AboutModel contains a Title property marked with [ViewData]. Title 属性设置为“关于”页面的标题:The Title property is set to the title of the About page:

public class AboutModel : PageModel
{
    [ViewData]
    public string Title { get; } = "About";

    public void OnGet()
    {
    }
}

在“关于”页面中,以模型属性的形式访问 Title 属性:In the About page, access the Title property as a model property:

<h1>@Model.Title</h1>

在布局中,从 ViewData 字典读取标题:In the layout, the title is read from the ViewData dictionary:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>@ViewData["Title"] - WebApplication</title>
    ...

TempDataTempData

ASP.NET 在控制器上公开了 TempData 属性。ASP.NET Core exposes the TempData property on a controller. 此属性存储未读取的数据。This property stores data until it's read. KeepPeek 方法可用于检查数据,而不执行删除。The Keep and Peek methods can be used to examine the data without deletion. 多个请求需要数据时,TempData 有助于进行重定向。TempData is useful for redirection, when data is needed for more than a single request.

下面的代码使用 TempData 设置 Message 的值:The following code sets the value of Message using TempData:

public class CreateDotModel : PageModel
{
    private readonly AppDbContext _db;

    public CreateDotModel(AppDbContext db)
    {
        _db = db;
    }

    [TempData]
    public string Message { get; set; }

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

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

        _db.Customers.Add(Customer);
        await _db.SaveChangesAsync();
        Message = $"Customer {Customer.Name} added";
        return RedirectToPage("./Index");
    }
}

Pages/Customers/Index.cshtml 文件中的以下标记使用 TempData 显示 Message 的值。The following markup in the Pages/Customers/Index.cshtml file displays the value of Message using TempData.

<h3>Msg: @Model.Message</h3>

Pages/Customers/Index.cshtml.cs 页面模型将 [TempData] 属性应用到 Message 属性。The Pages/Customers/Index.cshtml.cs page model applies the [TempData] attribute to the Message property.

[TempData]
public string Message { get; set; }

有关详细信息,请参阅 TempDataFor more information, see TempData .

针对一个页面的多个处理程序Multiple handlers per page

以下页面使用 asp-page-handler 标记帮助程序为两个处理程序生成标记:The following page generates markup for two handlers using the asp-page-handler Tag Helper:

@page
@model CreateFATHModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper 提交到不同的 URL。The form in the preceding example has two submit buttons, each using the FormActionTagHelper to submit to a different URL. asp-page-handlerasp-page 的配套属性。The asp-page-handler attribute is a companion to asp-page. asp-page-handler 生成提交到页面定义的各个处理程序方法的 URL。asp-page-handler generates URLs that submit to each of the handler methods defined by a page. 未指定 asp-page,因为示例已链接到当前页面。asp-page isn't specified because the sample is linking to the current page.

页面模型:The page model:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;

namespace RazorPagesContacts.Pages.Customers
{
    public class CreateFATHModel : PageModel
    {
        private readonly AppDbContext _db;

        public CreateFATHModel(AppDbContext db)
        {
            _db = db;
        }

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

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

            _db.Customers.Add(Customer);
            await _db.SaveChangesAsync();
            return RedirectToPage("/Index");
        }

        public async Task<IActionResult> OnPostJoinListUCAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }
            Customer.Name = Customer.Name?.ToUpperInvariant();
            return await OnPostJoinListAsync();
        }
    }
}

前面的代码使用已命名处理程序方法。The preceding code uses named handler methods. 已命名处理程序方法通过采用名称中 On<HTTP Verb> 之后及 Async 之前的文本(如果有)创建。Named handler methods are created by taking the text in the name after On<HTTP Verb> and before Async (if present). 在前面的示例中,页面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。In the preceding example, the page methods are OnPostJoinListAsync and OnPostJoinListUCAsync. 删除 OnPost 和 Async 后,处理程序名称为 JoinListJoinListUCWith OnPost and Async removed, the handler names are JoinList and JoinListUC.

<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />

使用前面的代码时,提交到 OnPostJoinListAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUsing the preceding code, the URL path that submits to OnPostJoinListAsync is https://localhost:5001/Customers/CreateFATH?handler=JoinList. 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUCThe URL path that submits to OnPostJoinListUCAsync is https://localhost:5001/Customers/CreateFATH?handler=JoinListUC.

自定义路由Custom routes

使用 @page 指令,执行以下操作:Use the @page directive to:

  • 指定页面的自定义路由。Specify a custom route to a page. 例如,使用 @page "/Some/Other/Path" 将“关于”页面的路由设置为 /Some/Other/PathFor example, the route to the About page can be set to /Some/Other/Path with @page "/Some/Other/Path".
  • 将段追加到页面的默认路由。Append segments to a page's default route. 例如,可使用 @page "item" 将“item”段添加到页面的默认路由。For example, an "item" segment can be added to a page's default route with @page "item".
  • 将参数追加到页面的默认路由。Append parameters to a page's default route. 例如,@page "{id}" 页面需要 ID 参数 idFor example, an ID parameter, id, can be required for a page with @page "{id}".

支持开头处以波形符 (~) 指定的相对于根目录的路径。A root-relative path designated by a tilde (~) at the beginning of the path is supported. 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。For example, @page "~/Some/Other/Path" is the same as @page "/Some/Other/Path".

如果你不喜欢 URL 中的查询字符串 ?handler=JoinList,请更改路由,将处理程序名称放在 URL 的路径部分。If you don't like the query string ?handler=JoinList in the URL, change the route to put the handler name in the path portion of the URL. 可以通过在 @page 指令后面添加使用双引号括起来的路由模板来自定义路由。The route can be customized by adding a route template enclosed in double quotes after the @page directive.

@page "{handler?}"
@model CreateRouteModel

<html>
<body>
    <p>
        Enter your name.
    </p>
    <div asp-validation-summary="All"></div>
    <form method="POST">
        <div>Name: <input asp-for="Customer.Name" /></div>
        <input type="submit" asp-page-handler="JoinList" value="Join" />
        <input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
    </form>
</body>
</html>

使用前面的代码时,提交到 OnPostJoinListAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUsing the preceding code, the URL path that submits to OnPostJoinListAsync is https://localhost:5001/Customers/CreateFATH/JoinList. 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUCThe URL path that submits to OnPostJoinListUCAsync is https://localhost:5001/Customers/CreateFATH/JoinListUC.

handler 前面的 ? 表示路由参数为可选。The ? following handler means the route parameter is optional.

配置和设置Configuration and settings

若要配置高级选项,请在 MVC 生成器上使用 AddRazorPagesOptions 扩展方法:To configure advanced options, use the extension method AddRazorPagesOptions on the MVC builder:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddRazorPagesOptions(options =>
        {
            options.RootDirectory = "/MyPages";
            options.Conventions.AuthorizeFolder("/MyPages/Admin");
        });
}

目前,可以使用 RazorPagesOptions 设置页面的根目录,或者为页面添加应用程序模型约定。Currently you can use the RazorPagesOptions to set the root directory for pages, or add application model conventions for pages. 通过这种方式,我们在将来会实现更多扩展功能。We'll enable more extensibility this way in the future.

若要预编译视图,请参阅 Razor 视图编译To precompile views, see Razor view compilation .

下载或查看示例代码.Download or view sample code.

请参阅 Razor Pages 入门这篇文章以本文为基础撰写。See Get started with Razor Pages, which builds on this introduction.

指定 Razor Pages 位于内容根目录中Specify that Razor Pages are at the content root

默认情况下,Razor Pages 位于 /Pages 目录的根位置。By default, Razor Pages are rooted in the /Pages directory. AddMvc 添加 WithRazorPagesAtContentRoot,以指定 Razor Pages 位于应用的根目录 (ContentRootPath):Add WithRazorPagesAtContentRoot to AddMvc to specify that your Razor Pages are at the content root (ContentRootPath) of the app:

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesAtContentRoot();

指定 Razor Pages 位于自定义根目录中Specify that Razor Pages are at a custom root directory

AddMvc 添加 WithRazorPagesRoot,以指定 Razor Pages 位于应用中的自定义根目录(提供相对路径):Add WithRazorPagesRoot to AddMvc to specify that your Razor Pages are at a custom root directory in the app (provide a relative path):

services.AddMvc()
    .AddRazorPagesOptions(options =>
    {
        ...
    })
    .WithRazorPagesRoot("/path/to/razor/pages");

其他资源Additional resources