作者: Rick Anderson Dave Brock Kirk Larkin

通过 Razor Pages 对基于页面的场景编码比使用控制器和视图更轻松、更高效。

若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门

本文档介绍 Razor Pages。 它并不是分步教程。 如果认为某些部分过于复杂,请参阅 Razor Pages 入门 。 有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介

此版本不是本文的最新版本。 若要切换到最新版本,请使用目录顶部的 ASP.NET Core 版本选择器。

如果选择器在较窄的浏览器窗口中不可见,请扩大窗口或选择垂直省略号 (⋮) >“目录”。

  • AddRazorPages 向应用添加 Razor Pages 的服务。
  • MapRazorPages IEndpointRouteBuilder 添加 Razor Pages 的终结点。
  • 请考虑一个基本页面:

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

    前面的代码与具有控制器和视图的 ASP.NET Core 应用中使用的 Razor 视图文件非常相似。 不同之处在于 @page 指令。 @page 将文件转换为 MVC 操作,这意味着它可以直接处理请求,而无需经过控制器。 @page 必须是页面上的第一个 Razor 指令。 @page 会影响其他 Razor 构造的行为。 Razor Pages 文件名有 .cshtml 后缀。

    将在以下两个文件中显示使用 PageModel 类的类似页面。 Pages/Index2.cshtml 文件:

    @page
    @using RazorPagesIntro.Pages
    @model Index2Model
    <h2>Separate page model</h2>
        @Model.Message
    

    Pages/Index2.cshtml.cs 页面模型:

    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 文件名称相同。 例如,前面的 Razor Page 的名称为 Pages/Index2.cshtml。 包含 PageModel 类的文件的名称为 Pages/Index2.cshtml.cs

    页面的 URL 路径的关联由页面在文件系统中的位置决定。 下表显示了 Razor Page 路径及匹配的 URL:

    文件名和路径 匹配的 URL

    编写基本窗体

    由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。 模型绑定标记帮助程序和 HTML 帮助程序可使用 Razor Page 类中定义的属性。 请参考为 Contact 模型实现的基本的“联系我们”窗体页面:

    在本文档中的示例中,DbContextStartup.cs 文件中进行初始化。

    内存中数据库需要 Microsoft.EntityFrameworkCore.InMemory NuGet 包。

    using Microsoft.EntityFrameworkCore;
    using RazorPagesContacts.Data;
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddRazorPages();
    builder.Services.AddDbContext<CustomerDbContext>(options =>
        options.UseInMemoryDatabase("name"));
    var app = builder.Build();
    if (!app.Environment.IsDevelopment())
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.MapRazorPages();
    app.Run();
    

    数据模型:

    using System.ComponentModel.DataAnnotations;
    namespace RazorPagesContacts.Models
        public class Customer
            public int Id { get; set; }
            [Required, StringLength(10)]
            public string? Name { get; set; }
    

    数据库上下文:

    using Microsoft.EntityFrameworkCore;
    namespace RazorPagesContacts.Data
        public class CustomerDbContext : DbContext
            public CustomerDbContext (DbContextOptions<CustomerDbContext> options)
                : base(options)
            public DbSet<RazorPagesContacts.Models.Customer> Customer => Set<RazorPagesContacts.Models.Customer>();
    

    Pages/Customers/Create.cshtml 视图文件:

    @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/Customers/Create.cshtml.cs 页面模型:

    public class CreateModel : PageModel
        private readonly Data.CustomerDbContext _context;
        public CreateModel(Data.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();
            if (Customer != null) _context.Customer.Add(Customer);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
    

    按照惯例,PageModel 类命名为 <PageName>Model并且它与页面位于同一个命名空间中。

    使用 PageModel 类,可以将页面的逻辑与其展示分离开来。 它定义了页面处理程序,用于处理发送到页面的请求和用于呈现页面的数据。 这种隔离可实现:

  • 通过依赖关系注入管理页面依赖项。
  • 页面包含 OnPostAsync 处理程序方法,它在 POST 请求上运行(当用户发布窗体时)。 可以添加任何 HTTP 谓词的处理程序方法。 最常见的处理程序是:

  • OnGet,用于初始化页面所需的状态。 在上面的代码中,OnGet 方法显示 CreateModel.cshtmlRazor Page。
  • OnPost,用于处理窗体提交。
  • Async 命名后缀为可选,但是按照惯例通常会将它用于异步函数。 前面的代码通常用于 Razor Pages。

    如果你熟悉使用控制器和视图的 ASP.NET 应用:

  • 前面示例中的 OnPostAsync 代码类似于典型的控制器代码。
  • 大多数 MVC 基元(例如模型绑定验证和操作结果)通过控制器和通过 Razor Pages 的运作方式相同。
  • 之前的 OnPostAsync 方法:

    [BindProperty]
    public Customer? Customer { get; set; }
    public async Task<IActionResult> OnPostAsync()
        if (!ModelState.IsValid)
            return Page();
        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    

    OnPostAsync 的基本流:

    检查验证错误。

  • 如果没有错误,则保存数据并重定向。
  • 如果有错误,则再次显示页面并附带验证消息。 很多情况下,都会在客户端上检测到验证错误,并且从不将它们提交到服务器。
  • Pages/Customers/Create.cshtml 视图文件:

    @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/Customers/Create.cshtml 中呈现的 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>
    

    在前面的代码中,发布窗体:

  • 对于有效数据:

  • OnPostAsync 处理程序方法调用 RedirectToPage 帮助程序方法。 RedirectToPage 返回 RedirectToPageResult 的实例。 RedirectToPage:

  • 是操作结果。
  • 类似于 RedirectToActionRedirectToRoute(用于控制器和视图)。
  • 针对页面自定义。 在前面的示例中,它将重定向到根索引页 (/Index)。 页面 URL 生成部分中详细介绍了 RedirectToPage
  • 对于传递给服务器的验证错误:

  • OnPostAsync 处理程序方法调用 Page 帮助程序方法。 Page 返回 PageResult 的实例。 返回 Page 的过程与控制器中的操作返回 View 的过程相似。 PageResult 是处理程序方法的默认返回类型。 返回 void 的处理程序方法将显示页面。
  • 在前面的示例中,在 ModelState.IsValid 中的值结果不返回 false 的情况下发布窗体。 在此示例中,客户端上不显示任何验证错误。 本文档的后面将介绍验证错误处理。
  • [BindProperty]
    public Customer? Customer { get; set; }
    public async Task<IActionResult> OnPostAsync()
        if (!ModelState.IsValid)
            return Page();
        if (Customer != null) _context.Customer.Add(Customer);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    
  • 对于客户端验证检测到的验证错误:

  • 数据不会发布到服务器。
  • 本文档的后面将介绍客户端验证。
  • Customer 属性使用 [BindProperty] 特性来选择加入模型绑定:

    public class CreateModel : PageModel
        private readonly Data.CustomerDbContext _context;
        public CreateModel(Data.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();
            if (Customer != null) _context.Customer.Add(Customer);
            await _context.SaveChangesAsync();
            return RedirectToPage("./Index");
    

    [BindProperty] 不应 用于包含不应由客户端更改的属性的模型。 有关详细信息,请参阅过度发布

    Razor Pages 只绑定带有非 GET 谓词的属性。 如果绑定到属性,则无需通过编写代码将 HTTP 数据转换为模型类型。 绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">) 来减少代码,并接受输入。

    出于安全原因,必须选择绑定 GET 请求数据以对模型属性进行分页。 请在将用户输入映射到属性前对其进行验证。 当处理依赖查询字符串或路由值的方案时,选择加入 GET 绑定非常有用。

    若要将属性绑定在 GET 请求上,请将 [BindProperty] 特性的 SupportsGet 属性设置为 true

    [BindProperty(SupportsGet = true)]
    

    有关详细信息,请参阅 ASP.NET Core Community Standup:GET 上的绑定讨论 (YouTube)

    查看 Pages/Customers/Create.cshtml 视图文件:

    @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 模型表达式。
  • 使用 @addTagHelper 提供标记帮助程序。
  • Index.cshtml 是主页:

    @page
    @model RazorPagesContacts.Pages.Customers.IndexModel
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    <h1>Contacts home page</h1>
    <form method="post">
        <table class="table">
            <thead>
                    <th>ID</th>
                    <th>Name</th>
                    <th></th>
            </thead>
            <tbody>
            @if (Model.Customers != null)
                foreach (var contact in Model.Customers)
                        <td> @contact.Id </td>
                        <td>@contact.Name</td>
                            <!-- <snippet_Edit> -->
                            <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                            <!-- </snippet_Edit> -->
                            <!-- <snippet_Delete> -->
                            <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                            <!-- </snippet_Delete> -->
            </tbody>
        </table>
        <a asp-page="Create">Create New</a>
    </form>
    

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

    public class IndexModel : PageModel
        private readonly Data.CustomerDbContext _context;
        public IndexModel(Data.CustomerDbContext context)
            _context = context;
        public IList<Customer>? Customers { get; set; }
        public async Task OnGetAsync()
            Customers = await _context.Customer.ToListAsync();
        public async Task<IActionResult> OnPostDeleteAsync(int id)
            var contact = await _context.Customer.FindAsync(id);
            if (contact != null)
                _context.Customer.Remove(contact);
                await _context.SaveChangesAsync();
            return RedirectToPage();
    

    Index.cshtml 文件包含以下标记:

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

    <a /a>定位点标记帮助程序使用 asp-route-{value} 属性生成“编辑”页面的链接。 此链接包含路由数据及联系人 ID。 例如 https://localhost:5001/Edit/1标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。

    Index.cshtml 文件包含用于为每个客户联系人创建删除按钮的标记:

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

    呈现的 HTML:

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

    删除按钮采用 HTML 呈现,其 formaction 包括参数:

  • asp-route-id 属性指定的客户联系人 ID。
  • asp-page-handler 属性指定的 handler
  • 选中按钮时,向服务器发送窗体 POST 请求。 按照惯例,根据方案 OnPost[handler]Async 基于 handler 参数的值来选择处理程序方法的名称。

    因为本示例中 handlerdelete,因此 OnPostDeleteAsync 处理程序方法用于处理 POST 请求。 如果 asp-page-handler 设置为其他值(如 remove),则选择名称为 OnPostRemoveAsync 的处理程序方法。

    public async Task<IActionResult> OnPostDeleteAsync(int id)
        var contact = await _context.Customer.FindAsync(id);
        if (contact != null)
            _context.Customer.Remove(contact);
            await _context.SaveChangesAsync();
        return RedirectToPage();
    

    OnPostDeleteAsync 方法:

  • 获取来自查询字符串的 id
  • 使用 FindAsync 查询客户联系人的数据库。
  • 如果找到客户联系人,则会将其删除,并更新数据库。
  • 调用 RedirectToPage,重定向到根索引页 (/Index)。
  • Edit.cshtml 文件

    @page "{id:int}"
    @model RazorPagesContacts.Pages.Customers.EditModel
        ViewData["Title"] = "Edit";
    <h1>Edit</h1>
    <h4>Customer</h4>
    <div class="row">
        <div class="col-md-4">
            <form method="post">
                <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                <input type="hidden" asp-for="Customer!.Id" />
                <div class="form-group">
                    <label asp-for="Customer!.Name" class="control-label"></label>
                    <input asp-for="Customer!.Name" class="form-control" />
                    <span asp-validation-for="Customer!.Name" class="text-danger"></span>
                <div class="form-group">
                    <input type="submit" value="Save" class="btn btn-primary" />
            </form>
        <a asp-page="./Index">Back to List</a>
    @section Scripts {
        @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    

    第一行包含 @page "{id:int}" 指令。 路由约束 "{id:int}" 告诉页面接受包含 int 路由数据的页面请求。 如果页面请求未包含可转换为 int 的路由数据,则运行时返回 HTTP 404(未找到)错误。 若要使 ID 可选,请将 ? 追加到路由约束:

    @page "{id:int?}"
    

    Edit.cshtml.cs 文件:

    public class EditModel : PageModel
        private readonly RazorPagesContacts.Data.CustomerDbContext _context;
        public EditModel(RazorPagesContacts.Data.CustomerDbContext context)
            _context = context;
        [BindProperty]
        public Customer? Customer { get; set; }
        public async Task<IActionResult> OnGetAsync(int? id)
            if (id == null)
                return NotFound();
            Customer = await _context.Customer.FirstOrDefaultAsync(m => m.Id == id);
            if (Customer == null)
                return NotFound();
            return Page();
        // To protect from overposting attacks, enable the specific properties you want to bind to.
        // For more details, see https://aka.ms/RazorPagesCRUD.
        public async Task<IActionResult> OnPostAsync()
            if (!ModelState.IsValid)
                return Page();
            if (Customer != null)
                _context.Attach(Customer).State = EntityState.Modified;
                    await _context.SaveChangesAsync();
                catch (DbUpdateConcurrencyException)
                    if (!CustomerExists(Customer.Id))
                        return NotFound();
                        throw;
            return RedirectToPage("./Index");
        private bool CustomerExists(int id)
            return _context.Customer.Any(e => e.Id == id);
    

    验证规则:

  • 在模型类中以声明方式指定。
  • 在应用中的所有位置强制执行。
  • System.ComponentModel.DataAnnotations 命名空间提供一组内置验证特性,可通过声明方式应用于类或属性。 DataAnnotations 还包含 [DataType] 等格式特性,有助于格式设置但不提供任何验证。

    请考虑 Customer 模型:

    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 视图文件:

    @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>
    

    前面的代码:

  • 包括 jQuery 和 jQuery 验证脚本。

  • 使用 <div /><span />标记帮助程序以实现:

  • 客户端验证。
  • 验证错误呈现。
  • 则会生成以下 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>
    

    如果在不使用名称值的情况下发布“创建”窗体,则将在窗体上显示错误消息“名称字段是必需的”。 如果客户端上已启用 JavaScript,浏览器会显示错误,而不会发布到服务器。

    [StringLength(10)] 特性在呈现的 HTML 上生成 data-val-length-max="10"data-val-length-max 阻止浏览器输入超过指定最大长度的内容。 如果使用 Fiddler 等工具来编辑和重播文章:

  • 对于长度超过 10 的名称。
  • 返回错误消息“‘名称’字段必须是最大长度为 10 的字符串。”
  • 考虑下列 Movie 模型:

    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    namespace RazorPagesMovie.Models
        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\s]*$")]
            [Required]
            [StringLength(30)]
            public string Genre { get; set; }
            [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
            [StringLength(5)]
            [Required]
            public string Rating { get; set; }
    

    验证特性指定要对应用这些特性的模型属性强制执行的行为:

  • RequiredMinimumLength 特性表示属性必须有值,但用户可输入空格来满足此验证。

  • RegularExpression 特性用于限制可输入的字符。 在上述代码中,即“Genre”(分类):

  • 只能使用字母。
  • 第一个字母必须为大写。 不允许使用空格、数字和特殊字符。
  • RegularExpression“Rating”(分级):

  • 要求第一个字符为大写字母。
  • 允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“分类”无效。
  • Range 特性将值限制在指定范围内。

  • StringLength 特性可以设置字符串属性的最大长度,以及可选的最小长度。

  • 从本质上来说,需要值类型(如 decimalintfloatDateTime),但不需要 [Required] 特性。

    Movie 模型的“创建”页面显示无效值错误:

    有关详细信息,请参阅:

  • 将验证添加到电影应用
  • ASP.NET Core 中的模型验证.
  • CSS 隔离

    将 CSS 样式隔离到各个页面、视图和组件以减少或避免:

  • 依赖难以维护的全局样式。
  • 嵌套内容中的样式冲突。
  • 若要为页面或视图添加限定范围的 CSS 文件,请将 CSS 样式置于与 .cshtml 文件的名称匹配的配套 .cshtml.css 文件中。 在下面的示例中,Index.cshtml.css 文件提供只应用于 Index.cshtml 页面或视图的 CSS 样式。

    Pages/Index.cshtml.css (Razor Pages) 或 Views/Index.cshtml.css (MVC):

    color: red;

    CSS 隔离在生成时发生。 框架会重写 CSS 选择器,以匹配应用页面或视图所呈现的标记。 重写的 CSS 样式作为静态资产 {APP ASSEMBLY}.styles.css 进行捆绑和生成。 占位符 {APP ASSEMBLY} 是项目的程序集名称。 指向捆绑 CSS 样式的链接放置在应用的布局中。

    在应用 Pages/Shared/_Layout.cshtml (Razor Pages) 或 Views/Shared/_Layout.cshtml (MVC) 的 <head> 内容中,添加或确认是否存在指向捆绑 CSS 样式的链接:

    <link rel="stylesheet" href="~/{APP ASSEMBLY}.styles.css" />
    

    在下面的示例中,应用的程序集名称为 WebApp

    <link rel="stylesheet" href="WebApp.styles.css" />
    

    在限定范围的 CSS 文件中定义的样式仅应用于匹配文件的呈现输出。 在上面的示例中,在应用的其他位置定义的任何 h1 CSS 声明都不会与 Index 标头样式冲突。 CSS 样式级联和继承规则对限定范围的 CSS 文件仍然有效。 例如,直接应用于 Index.cshtml 文件中的 <h1> 元素的样式会替代 Index.cshtml.css 中限定范围的 CSS 文件的样式。

    为了保证发生捆绑时的 CSS 样式隔离,不支持在 Razor 代码块中导入 CSS。

    CSS 隔离仅适用于 HTML 元素。 标记帮助程序不支持 CSS 隔离。

    在捆绑 CSS 文件中,每个页面、视图或 Razor 组件都与格式为 b-{STRING} 的范围标识符相关联,其中 {STRING} 占位符是框架生成的十个字符的字符串。 下面的示例提供了 Razor Pages 应用 Index 页面中前面 <h1> 元素的样式:

    /* /Pages/Index.cshtml.rz.scp.css */
    h1[b-3xxtam6d07] {
        color: red;
    

    在从捆绑文件应用 CSS 样式的 Index 页面中,范围标识符追加为 HTML 属性:

    <h1 b-3xxtam6d07>
    

    标识符对应用是唯一的。 在生成时,会使用约定 {STATIC WEB ASSETS BASE PATH}/Project.lib.scp.css 创建项目捆绑包,其中占位符 {STATIC WEB ASSETS BASE PATH} 是静态 Web 资产的基路径。

    如果利用了其他项目(如 NuGet 包或Razor 类库),则捆绑的文件将发生以下情况:

  • 使用 CSS 导入引用这些样式。
  • 不会发布为使用这些样式的应用的静态 Web 资产。
  • CSS 预处理器支持

    利用 CSS 预处理器的变量、嵌套、模块、混合和继承等功能,可有效改进 CSS 开发。 虽然 CSS 隔离并不原生支持 CSS 预处理器(如 Sass 或 Less),但只要在生成过程中框架重写 CSS 选择器的步骤之前进行预处理器编译,就可以无缝集成 CSS 预处理器。 例如,使用 Visual Studio 将现有预处理器编译配置为 Visual Studio 任务运行程序资源管理器中的“生成前”任务。

    许多第三方 NuGet 包(如 AspNetCore.SassCompiler)都可以在生成过程开始时编译 SASS/SCSS 文件,再进行 CSS 隔离,而无需其他配置。

    CSS 隔离配置

    CSS 隔离允许在某些高级场景(例如依赖于现有工具或工作流)下进行配置。

    自定义范围标识符格式

    在此部分中,{Pages|Views} 占位符为 Razor Pages 应用的 Pages 或 MVC 应用的 Views

    默认情况下,范围标识符使用格式 b-{STRING},其中 {STRING} 占位符是框架生成的十个字符的字符串。 若要自定义范围标识符格式,请将项目文件更新为所需模式:

    <ItemGroup>
      <None Update="{Pages|Views}/Index.cshtml.css" CssScope="custom-scope-identifier" />
    </ItemGroup>
    

    在上面的示例中,为 Index.cshtml.css 生成的 CSS 将其范围标识符从 b-{STRING} 更改为了 custom-scope-identifier

    使用范围标识符来实现与限定范围的 CSS 文件的继承。 在下面的项目文件示例中,BaseView.cshtml.css 文件包含跨视图的通用样式。 DerivedView.cshtml.css 文件继承了这些样式。

    <ItemGroup>
      <None Update="{Pages|Views}/BaseView.cshtml.css" CssScope="custom-scope-identifier" />
      <None Update="{Pages|Views}/DerivedView.cshtml.css" CssScope="custom-scope-identifier" />
    </ItemGroup>
    

    使用通配符 (*) 运算符跨多个文件共享范围标识符:

    <ItemGroup>
      <None Update="{Pages|Views}/*.cshtml.css" CssScope="custom-scope-identifier" />
    </ItemGroup>
    

    更改静态 Web 资产的基路径

    限定范围的 CSS 文件在应用的根目录生成。 在项目文件中,请使用 StaticWebAssetBasePath 属性来更改默认路径。 下面的示例将限定范围的 CSS 文件以及应用的其余资产放在 _content 路径:

    <PropertyGroup>
      <StaticWebAssetBasePath>_content/$(PackageId)</StaticWebAssetBasePath>
    </PropertyGroup>
    

    禁用自动捆绑

    若要禁用框架在运行时发布和加载限定范围的文件,请使用 DisableScopedCssBundling 属性。 使用此属性时,由其他工具或进程从 obj 目录中捕获隔离的 CSS 文件,并在运行时发布和加载这些文件:

    <PropertyGroup>
      <DisableScopedCssBundling>true</DisableScopedCssBundling>
    </PropertyGroup>
    

    Razor 类库 (RCL) 支持

    Razor 类库 (RCL) 提供隔离的样式,<link> 标记的 href 属性指向 {STATIC WEB ASSET BASE PATH}/{PACKAGE ID}.bundle.scp.css,其中占位符为:

  • {STATIC WEB ASSET BASE PATH}:静态 Web 资产基路径。
  • {PACKAGE ID}:库的包标识符。 如果项目文件中未指定包标识符,则包标识符默认为项目的程序集名称。
  • 如下示例中:

  • 静态 Web 资产基路径为 _content/ClassLib
  • 类库的程序集名称为 ClassLib
  • Pages/Shared/_Layout.cshtml (Razor Pages) 或 Views/Shared/_Layout.cshtml (MVC):

    <link href="_content/ClassLib/ClassLib.bundle.scp.css" rel="stylesheet">
    

    有关 RCL 的详细信息,请参阅以下文章:

  • ASP.NET Core 的类库中的可重用 Razor UI
  • 使用 Razor 类库 (RCL) 中的 ASP.NET Core Razor 组件
  • 有关 Blazor CSS 隔离的信息,请参阅 ASP.NET Core Blazor CSS 隔离

    使用 OnGet 处理程序回退来处理 HEAD 请求

    HEAD 请求可检索特定资源的标头。 与 GET 请求不同,HEAD 请求不返回响应正文。

    通常,针对 HEAD 请求创建和调用 OnHead 处理程序:

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

    如果未定义 OnHead 处理程序,则 Razor Pages 会回退到调用 OnGet 处理程序。

    XSRF/CSRF 和 Razor Pages

    Razor Pages 由 防伪造验证保护。 FormTagHelper 将防伪造令牌注入 HTML 窗体元素。

    将布局、分区、模板和标记帮助程序用于 Razor Pages

    页面可使用 Razor 视图引擎的所有功能。 布局、分区、模板、标记帮助程序、_ViewStart.cshtml_ViewImports.cshtml 的工作方式与它们在传统的 Razor 视图中的工作方式相同。

    让我们使用其中的一些功能来整理此页面。

    Pages/Shared/_Layout.cshtml 添加布局页面

    <!DOCTYPE html>
        <title>RP Sample</title>
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </head>
        <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>
    
  • 控制每个页面的布局(页面选择退出布局时除外)。
  • 导入 HTML 结构,例如 JavaScript 和样式表。
  • 调用 @RenderBody() 时,呈现 Razor Page 的内容。
  • 有关详细信息,请参阅布局页面

    Pages/_ViewStart.cshtml 中设置 Layout 属性:

    Layout = "_Layout";

    布局位于“页面/共享”文件夹中。 页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。 可以从“Pages”文件夹下的任意 Razor 页面使用“Pages/Shared”文件夹中的布局。

    布局文件应位于 Pages/Shared 文件夹中。

    建议不要将布局文件放在“视图/共享”文件夹中。 视图/共享 是一种 MVC 视图模式。 Razor Pages 旨在依赖文件夹层次结构,而非路径约定。

    Razor Page 中的视图搜索包含“Pages”文件夹。 用于 MVC 控制器和传统 Razor 视图的布局、模板和分区可正常运行。

    添加 Pages/_ViewImports.cshtml 文件:

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

    本教程的后续部分中将介绍 @namespace@addTagHelper 指令将内置标记帮助程序引入“页面”文件夹中的所有页面。

    页面上设置的 @namespace 指令:

    @page
    @namespace RazorPagesIntro.Pages.Customers
    @model NameSpaceModel
    <h2>Name space</h2>
        @Model.Message
    

    @namespace 指令将为页面设置命名空间。 @model 指令无需包含命名空间。

    _ViewImports.cshtml 中包含 @namespace 指令后,指定的命名空间将为在导入 @namespace 指令的页面中生成的命名空间提供前缀。 生成的命名空间的剩余部分(后缀部分)是包含 _ViewImports.cshtml 的文件夹与包含页面的文件夹之间以点分隔的相对路径。

    例如,PageModelPages/Customers/Edit.cshtml.cs 显式设置命名空间:

    namespace RazorPagesContacts.Pages
        public class EditModel : PageModel
            private readonly AppDbContext _db;
            public EditModel(AppDbContext db)
                _db = db;
            // Code removed for brevity.
    

    Pages/_ViewImports.cshtml 文件设置以下命名空间:

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

    Pages/Customers/Edit.cshtmlRazor Page 生成的命名空间与 PageModel 类相同。

    @namespace 也适用于传统 Razor 视图。

    考虑 Pages/Customers/Create.cshtml 视图文件:

    @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/Customers/Create.cshtml 视图文件和前面的布局文件:

    @page
    @model CreateModel
    <p>Enter a customer name:</p>
    <form method="post">
        Name:
        <input asp-for="Customer.Name" />
        <input type="submit" />
    </form>
    

    在前面的代码中,_ViewImports.cshtml 导入了命名空间和标记帮助程序。 布局文件导入了 JavaScript 文件。

    Razor Pages 初学者项目包含 Pages/_ValidationScriptsPartial.cshtml,它与客户端验证联合。

    有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图

    页面的 URL 生成

    之前显示的 Create 页面使用 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");
    

    应用具有以下文件/文件夹结构:

  • /Pages

  • Index.cshtml

  • Privacy.cshtml

  • /Customers

  • Create.cshtml
  • Edit.cshtml
  • Index.cshtml
  • 成功后,Pages/Customers/Create.cshtmlPages/Customers/Edit.cshtml 页面将重定向到 Pages/Customers/Index.cshtml。 字符串 ./Index 是用于访问前一页的相对页名称。 它用于生成 Pages/Customers/Index.cshtml 页面的 URL。 例如:

  • Url.Page("./Index", ...)
  • <a asp-page="./Index">Customers Index Page</a>
  • RedirectToPage("./Index")
  • 绝对页名称 /Index 用于生成 Pages/Index.cshtml 页面的 URL。 例如:

  • Url.Page("/Index", ...)
  • <a asp-page="/Index">Home Index Page</a>
  • RedirectToPage("/Index")
  • 页面名称是从根“/Pages”文件夹到页面的路径(包含前导 /,例如 /Index)。 与硬编码 URL 相比,前面的 URL 生成示例提供了改进的选项和功能。 URL 生成使用路由,并且可以根据目标路径定义路由的方式生成参数并对参数编码。

    页面的 URL 生成支持相对名称。 下表显示了 Pages/Customers/Create.cshtml 中不同的 RedirectToPage 参数选择的索引页。

    RedirectToPage(x)

    RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是相对名称。 结合RedirectToPage 参数与当前页的路径来计算目标页面的名称。

    构建结构复杂的站点时,相对名称链接很有用。 如果使用相对名称链接文件夹中的页面:

  • 重命名文件夹不会破坏相对链接。
  • 链接不会中断,因为它们不包含文件夹名称。
  • 若要重定向到不同区域中的页面,请指定区域:

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

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

    ViewData 特性

    可以通过 ViewDataAttribute 将数据传递到页面。 具有 [ViewData] 特性的属性从 ViewDataDictionary 保存和加载值。

    在下面的示例中,AboutModel[ViewData] 特性应用于 Title 属性:

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

    在“关于”页面中,以模型属性的形式访问 Title 属性:

    <h1>@Model.Title</h1>
    

    在布局中,从 ViewData 字典读取标题:

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

    TempData

    ASP.NET Core 公开 TempData。 此属性存储未读取的数据。 KeepPeek 方法可用于检查数据,而不执行删除。 TempData 在多个请求需要数据的情况下对重定向很有用。

    下面的代码使用 TempData 设置 Message 的值:

    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 的值。

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

    Pages/Customers/Index.cshtml.cs 页面模型将 [TempData] 属性应用到 Message 属性。

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

    有关详细信息,请参阅 TempData

    针对一个页面的多个处理程序

    以下页面使用 asp-page-handler 标记帮助程序为两个处理程序生成标记:

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

    前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper 提交到不同的 URL。 asp-page-handlerasp-page 的配套属性。 asp-page-handler 生成提交到页面定义的各个处理程序方法的 URL。 未指定 asp-page,因为示例已链接到当前页面。

    页面模型:

    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();
    

    前面的代码使用已命名处理程序方法。 已命名处理程序方法通过采用名称中 On<HTTP Verb> 之后及 Async 之前的文本(如果有)创建。 在前面的示例中,页面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。 删除 OnPost 和 Async 后,处理程序名称为 JoinListJoinListUC

    <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=JoinList。 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC

    自定义路由

    使用 @page 指令,执行以下操作:

  • 指定页面的自定义路由。 例如,使用 @page "/Some/Other/Path" 将“关于”页面的路由设置为 /Some/Other/Path
  • 将段追加到页面的默认路由。 例如,可使用 @page "item" 将“item”段添加到页面的默认路由。
  • 将参数追加到页面的默认路由。 例如,@page "{id}" 页面需要 ID 参数 id
  • 支持开头处以波形符 (~) 指定的相对于根目录的路径。 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。

    如果你不喜欢 URL 中的查询字符串 ?handler=JoinList,请更改路由,将处理程序名称放在 URL 的路径部分。 可以通过在 @page 指令后面添加使用双引号括起来的路由模板来自定义路由。

    @page "{handler?}"
    @model CreateRouteModel
            Enter your name.
        <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/JoinList。 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUC

    handler 前面的 ? 表示路由参数为可选。

    JavaScript (JS) 文件的并置

    为页面、视图和 Razor 组件并置 JavaScript (JS) 文件是组织应用中脚本的简便方法。

    使用以下文件名扩展约定并置 JS 文件:

  • Razor Pages 应用的页面和 MVC 应用的视图:.cshtml.js。 示例:
    • 对于位于 Pages/Index.cshtml 的 Razor Pages 应用的 Index 页面,使用 Pages/Index.cshtml.js
    • 对于位于 Views/Home/Index.cshtml 的 MVC 应用的 Index 视图,使用 Views/Home/Index.cshtml.js
    • Blazor 应用的 Razor 组件:.razor.js。 示例:对于位于 Pages/Index.razorIndex 组件,使用 Pages/Index.razor.js
    • 使用项目中文件的路径可以公开寻址并置的 JS 文件:

    • 来自应用中并置的脚本文件的页面、视图和组件:

      {PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • 占位符 {PATH} 是页面、视图或组件的路径。
    • 占位符 {PAGE, VIEW, OR COMPONENT} 是页面、视图或组件。
    • 占位符 {EXTENSION} 与页面、视图或组件的扩展名匹配,为 razorcshtml
    • Razor Pages 示例:

      Index 页面的 JS 文件放置在 Index 页面 (Pages/Index.cshtml) 旁边的 Pages 文件夹 (Pages/Index.cshtml.js) 中。 在 Index 页面中,脚本在 Pages 文件夹中的路径引用:

      @section Scripts {
        <script src="~/Pages/Index.cshtml.js"></script>
      

      发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本将移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js,其中 {TARGET FRAMEWORK MONIKER} 占位符是目标框架名字对象 (TFM)。 无需更改 Index 页面中脚本的相对 URL。

      Blazor 示例:

      Index 组件的 JS 文件放置在 Index 组件 (Pages/Index.razor) 旁边的 Pages 文件夹 (Pages/Index.razor.js) 中。 在 Index 组件中,脚本在 Pages 文件夹中的路径引用。

      Pages/Index.razor.js:

      export function showPrompt(message) {
        return prompt(message, 'Type anything here');
      

      Index 组件 (Pages/Index.razor) 的 OnAfterRenderAsync 方法中:

      module = await JS.InvokeAsync<IJSObjectReference>(
          "import", "./Pages/Index.razor.js");
      

      发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本被移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.razor.js,其中 {TARGET FRAMEWORK MONIKER} 占位符是目标框架名字对象 (TFM)。 无需更改 Index 组件中脚本的相对 URL。

    • 对于 Razor 类库 (RCL) 提供的脚本:

      _content/{PACKAGE ID}/{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js

    • 占位符 {PACKAGE ID} 是 RCL 的包标识符(或由应用引用的类库的库名称)。
    • 占位符 {PATH} 是页面、视图或组件的路径。 如果 Razor 组件位于 RCL 的根目录下,则不包括路径段。
    • 占位符 {PAGE, VIEW, OR COMPONENT} 是页面、视图或组件。
    • 占位符 {EXTENSION} 与页面、视图或组件的扩展名匹配,为 razorcshtml
    • 在以下 Blazor 应用示例中:

    • RCL 的包标识符是 AppJS
    • 将为 Index 组件 (Index.razor) 加载模块的脚本。
    • Index 组件位于 RCL 的 Pages 文件夹中。
    • var module = await JS.InvokeAsync<IJSObjectReference>("import", 
          "./_content/AppJS/Pages/Index.razor.js");
      

      高级配置和设置

      大多数应用不需要以下部分中的配置和设置。

      要配置高级选项,请使用 AddRazorPages 重载,该重载配置 RazorPagesOptions

      using Microsoft.EntityFrameworkCore;
      using RazorPagesContacts.Data;
      var builder = WebApplication.CreateBuilder(args);
      builder.Services.AddRazorPages(options =>
          options.RootDirectory = "/MyPages";
          options.Conventions.AuthorizeFolder("/MyPages/Admin");
      builder.Services.AddDbContext<CustomerDbContext>(options =>
          options.UseInMemoryDatabase("name"));
      var app = builder.Build();
      if (!app.Environment.IsDevelopment())
          app.UseExceptionHandler("/Error");
          app.UseHsts();
      app.UseHttpsRedirection();
      app.UseStaticFiles();
      app.UseRouting();
      app.UseAuthorization();
      app.MapRazorPages();
      app.Run();
      

      使用 RazorPagesOptions 设置页面的根目录,或者为页面添加应用程序模型约定。 有关约定的详细信息,请参阅 Razor Pages 授权约定.

      若要预编译视图,请参阅 Razor 视图编译

      指定 Razor Pages 位于内容根目录中

      默认情况下,Razor Pages 位于 /Pages 目录的根位置。 添加 WithRazorPagesAtContentRoot 以指定 Razor Pages 位于应用的内容根 (ContentRootPath):

      using Microsoft.EntityFrameworkCore;
      using RazorPagesContacts.Data;
      var builder = WebApplication.CreateBuilder(args);
      builder.Services.AddRazorPages(options =>
          options.Conventions.AuthorizeFolder("/MyPages/Admin");
        .WithRazorPagesAtContentRoot();
      builder.Services.AddDbContext<CustomerDbContext>(options =>
          options.UseInMemoryDatabase("name"));
      var app = builder.Build();
      if (!app.Environment.IsDevelopment())
          app.UseExceptionHandler("/Error");
          app.UseHsts();
      app.UseHttpsRedirection();
      app.UseStaticFiles();
      app.UseRouting();
      app.UseAuthorization();
      app.MapRazorPages();
      app.Run();
      

      指定 Razor Pages 位于自定义根目录中

      添加 WithRazorPagesRoot,以指定 Razor Pages 位于应用中自定义根目录位置(提供相对路径):

      using Microsoft.EntityFrameworkCore;
      using RazorPagesContacts.Data;
      var builder = WebApplication.CreateBuilder(args);
      builder.Services.AddRazorPages(options =>
          options.Conventions.AuthorizeFolder("/MyPages/Admin");
        .WithRazorPagesRoot("/path/to/razor/pages");
      builder.Services.AddDbContext<CustomerDbContext>(options =>
          options.UseInMemoryDatabase("name"));
      var app = builder.Build();
      if (!app.Environment.IsDevelopment())
          app.UseExceptionHandler("/Error");
          app.UseHsts();
      app.UseHttpsRedirection();
      app.UseStaticFiles();
      app.UseRouting();
      app.UseAuthorization();
      app.MapRazorPages();
      app.Run();
      
    • 请参阅 Razor Pages 入门这篇文章以本文为基础撰写。
    • 授权属性和 Razor Pages
    • 下载或查看示例代码
    • ASP.NET Core 概述
    • ASP.NET Core 的 Razor 语法参考
    • ASP.NET Core 中的区域
    • 教程:在 ASP.NET Core 中开始使用 Razor Pages
    • Razor ASP.NET Core 中的 Pages 授权约定
    • Razor ASP.NET Core 中的 Pages 路由和应用约定
    • Razor ASP.NET Core 中的 Pages 单元测试
    • ASP.NET Core 中的分部视图
    • 预呈现和集成 ASP.NET Core Razor 组件
    • public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) services.AddRazorPages(); public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseExceptionHandler("/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => endpoints.MapRazorPages();

      请考虑一个基本页面:

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

      前面的代码与具有控制器和视图的 ASP.NET Core 应用中使用的 Razor 视图文件非常相似。 不同之处在于 @page 指令。 @page 使文件转换为一个 MVC 操作 ,这意味着它将直接处理请求,而无需通过控制器处理。 @page 必须是页面上的第一个 Razor 指令。 @page 会影响其他 Razor 构造的行为。 Razor Pages 文件名有 .cshtml 后缀。

      将在以下两个文件中显示使用 PageModel 类的类似页面。 Pages/Index2.cshtml 文件:

      @page
      @using RazorPagesIntro.Pages
      @model Index2Model
      <h2>Separate page model</h2>
          @Model.Message
      

      Pages/Index2.cshtml.cs 页面模型:

      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 文件名称相同。 例如,前面的 Razor Page 的名称为 Pages/Index2.cshtml。 包含 PageModel 类的文件的名称为 Pages/Index2.cshtml.cs

      页面的 URL 路径的关联由页面在文件系统中的位置决定。 下表显示了 Razor Page 路径及匹配的 URL:

      文件名和路径 匹配的 URL

      编写基本窗体

      由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。 模型绑定标记帮助程序和 HTML 帮助程序均只可用于 Razor Page 类中定义的属性。 请参考为 Contact 模型实现的基本的“联系我们”窗体页面:

      在本文档中的示例中,DbContextStartup.cs 文件中进行初始化。

      内存中数据库需要 Microsoft.EntityFrameworkCore.InMemory NuGet 包。

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

      数据模型:

      using System.ComponentModel.DataAnnotations;
      namespace RazorPagesContacts.Models
          public class Customer
              public int Id { get; set; }
              [Required, StringLength(10)]
              public string Name { get; set; }
      

      数据库上下文:

      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 视图文件:

      @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 页面模型:

      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并且它与页面位于同一个命名空间中。

      使用 PageModel 类,可以将页面的逻辑与其展示分离开来。 它定义了页面处理程序,用于处理发送到页面的请求和用于呈现页面的数据。 这种隔离可实现:

    • 通过依赖关系注入管理页面依赖项。
    • 页面包含 OnPostAsync 处理程序方法,它在 POST 请求上运行(当用户发布窗体时)。 可以添加任何 HTTP 谓词的处理程序方法。 最常见的处理程序是:

    • OnGet,用于初始化页面所需的状态。 在上面的代码中,OnGet 方法显示 CreateModel.cshtmlRazor Page。
    • OnPost,用于处理窗体提交。
    • Async 命名后缀为可选,但是按照惯例通常会将它用于异步函数。 前面的代码通常用于 Razor Pages。

      如果你熟悉使用控制器和视图的 ASP.NET 应用:

    • 前面示例中的 OnPostAsync 代码类似于典型的控制器代码。
    • 大多数 MVC 基元(例如模型绑定验证和操作结果)通过控制器和通过 Razor Pages 的运作方式相同。
    • 之前的 OnPostAsync 方法:

      public async Task<IActionResult> OnPostAsync()
          if (!ModelState.IsValid)
              return Page();
          _context.Customers.Add(Customer);
          await _context.SaveChangesAsync();
          return RedirectToPage("./Index");
      

      OnPostAsync 的基本流:

      检查验证错误。

    • 如果没有错误,则保存数据并重定向。
    • 如果有错误,则再次显示页面并附带验证消息。 很多情况下,都会在客户端上检测到验证错误,并且从不将它们提交到服务器。
    • Pages/Create.cshtml 视图文件:

      @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:

      <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>
      

      在前面的代码中,发布窗体:

    • 对于有效数据:

    • OnPostAsync 处理程序方法调用 RedirectToPage 帮助程序方法。 RedirectToPage 返回 RedirectToPageResult 的实例。 RedirectToPage:

    • 是操作结果。
    • 类似于 RedirectToActionRedirectToRoute(用于控制器和视图)。
    • 针对页面自定义。 在前面的示例中,它将重定向到根索引页 (/Index)。 页面 URL 生成部分中详细介绍了 RedirectToPage
    • 对于传递给服务器的验证错误:

    • OnPostAsync 处理程序方法调用 Page 帮助程序方法。 Page 返回 PageResult 的实例。 返回 Page 的过程与控制器中的操作返回 View 的过程相似。 PageResult 是处理程序方法的默认返回类型。 返回 void 的处理程序方法将显示页面。
    • 在前面的示例中,在 ModelState.IsValid 中的值结果不返回 false 的情况下发布窗体。 在此示例中,客户端上不显示任何验证错误。 本文档的后面将介绍验证错误处理。
    • public async Task<IActionResult> OnPostAsync()
          if (!ModelState.IsValid)
              return Page();
          _context.Customers.Add(Customer);
          await _context.SaveChangesAsync();
          return RedirectToPage("./Index");
      
    • 对于客户端验证检测到的验证错误:

    • 数据不会发布到服务器。
    • 本文档的后面将介绍客户端验证。
    • Customer 属性使用 [BindProperty] 特性来选择加入模型绑定:

      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] 不应 用于包含不应由客户端更改的属性的模型。 有关详细信息,请参阅过度发布

      Razor Pages 只绑定带有非 GET 谓词的属性。 如果绑定到属性,则无需通过编写代码将 HTTP 数据转换为模型类型。 绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">) 来减少代码,并接受输入。

      出于安全原因,必须选择绑定 GET 请求数据以对模型属性进行分页。 请在将用户输入映射到属性前对其进行验证。 当处理依赖查询字符串或路由值的方案时,选择加入 GET 绑定非常有用。

      若要将属性绑定在 GET 请求上,请将 [BindProperty] 特性的 SupportsGet 属性设置为 true

      [BindProperty(SupportsGet = true)]
      

      有关详细信息,请参阅 ASP.NET Core Community Standup:GET 上的绑定讨论 (YouTube)

      查看 Pages/Create.cshtml 视图文件:

      @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 模型表达式。
    • 使用 @addTagHelper 提供标记帮助程序。
    • Index.cshtml 是主页:

      @page
      @model RazorPagesContacts.Pages.Customers.IndexModel
      @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
      <h1>Contacts home page</h1>
      <form method="post">
          <table class="table">
              <thead>
                      <th>ID</th>
                      <th>Name</th>
                      <th></th>
              </thead>
              <tbody>
                  @foreach (var contact in Model.Customer)
                          <td> @contact.Id  </td>
                          <td>@contact.Name</td>
                              <!-- <snippet_Edit> -->
                              <a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
                              <!-- </snippet_Edit> -->
                              <!-- <snippet_Delete> -->
                              <button type="submit" asp-page-handler="delete" asp-route-id="@contact.Id">delete</button>
                              <!-- </snippet_Delete> -->
              </tbody>
          </table>
          <a asp-page="Create">Create New</a>
      </form>
      

      关联的 PageModel 类 (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 文件包含以下标记:

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

      <a /a>定位点标记帮助程序使用 asp-route-{value} 属性生成“编辑”页面的链接。 此链接包含路由数据及联系人 ID。 例如 https://localhost:5001/Edit/1标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。

      Index.cshtml 文件包含用于为每个客户联系人创建删除按钮的标记:

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

      呈现的 HTML:

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

      删除按钮采用 HTML 呈现,其 formaction 包括参数:

    • asp-route-id 属性指定的客户联系人 ID。
    • asp-page-handler 属性指定的 handler
    • 选中按钮时,向服务器发送窗体 POST 请求。 按照惯例,根据方案 OnPost[handler]Async 基于 handler 参数的值来选择处理程序方法的名称。

      因为本示例中 handlerdelete,因此 OnPostDeleteAsync 处理程序方法用于处理 POST 请求。 如果 asp-page-handler 设置为其他值(如 remove),则选择名称为 OnPostRemoveAsync 的处理程序方法。

      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 方法:

    • 获取来自查询字符串的 id
    • 使用 FindAsync 查询客户联系人的数据库。
    • 如果找到客户联系人,则会将其删除,并更新数据库。
    • 调用 RedirectToPage,重定向到根索引页 (/Index)。
    • Edit.cshtml 文件

      @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" />
              <label asp-for="Customer.Name"></label>
                  <input asp-for="Customer.Name" />
                  <span asp-validation-for="Customer.Name"></span>
              <button type="submit">Save</button>
      </form>
      

      第一行包含 @page "{id:int}" 指令。 路由约束 "{id:int}" 告诉页面接受包含 int 路由数据的页面请求。 如果页面请求未包含可转换为 int 的路由数据,则运行时返回 HTTP 404(未找到)错误。 若要使 ID 可选,请将 ? 追加到路由约束:

      @page "{id:int?}"
      

      Edit.cshtml.cs 文件:

      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;
                  await _context.SaveChangesAsync();
              catch (DbUpdateConcurrencyException)
                  throw new Exception($"Customer {Customer.Id} not found!");
              return RedirectToPage("./Index");
      

      验证规则:

    • 在模型类中以声明方式指定。
    • 在应用中的所有位置强制执行。
    • System.ComponentModel.DataAnnotations 命名空间提供一组内置验证特性,可通过声明方式应用于类或属性。 DataAnnotations 还包含 [DataType] 等格式特性,有助于格式设置但不提供任何验证。

      请考虑 Customer 模型:

      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 视图文件:

      @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>
      

      前面的代码:

    • 包括 jQuery 和 jQuery 验证脚本。

    • 使用 <div /><span />标记帮助程序以实现:

    • 客户端验证。
    • 验证错误呈现。
    • 则会生成以下 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>
      

      如果在不使用名称值的情况下发布“创建”窗体,则将在窗体上显示错误消息“名称字段是必需的”。 如果客户端上已启用 JavaScript,浏览器会显示错误,而不会发布到服务器。

      [StringLength(10)] 特性在呈现的 HTML 上生成 data-val-length-max="10"data-val-length-max 阻止浏览器输入超过指定最大长度的内容。 如果使用 Fiddler 等工具来编辑和重播文章:

    • 对于长度超过 10 的名称。
    • 返回错误消息“‘名称’字段必须是最大长度为 10 的字符串。”
    • 考虑下列 Movie 模型:

      using System;
      using System.ComponentModel.DataAnnotations;
      using System.ComponentModel.DataAnnotations.Schema;
      namespace RazorPagesMovie.Models
          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\s]*$")]
              [Required]
              [StringLength(30)]
              public string Genre { get; set; }
              [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")]
              [StringLength(5)]
              [Required]
              public string Rating { get; set; }
      

      验证特性指定要对应用这些特性的模型属性强制执行的行为:

    • RequiredMinimumLength 特性表示属性必须有值,但用户可输入空格来满足此验证。

    • RegularExpression 特性用于限制可输入的字符。 在上述代码中,即“Genre”(分类):

    • 只能使用字母。
    • 第一个字母必须为大写。 不允许使用空格、数字和特殊字符。
    • RegularExpression“Rating”(分级):

    • 要求第一个字符为大写字母。
    • 允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“分类”无效。
    • Range 特性将值限制在指定范围内。

    • StringLength 特性可以设置字符串属性的最大长度,以及可选的最小长度。

    • 从本质上来说,需要值类型(如 decimalintfloatDateTime),但不需要 [Required] 特性。

      Movie 模型的“创建”页面显示无效值错误:

      有关详细信息,请参阅:

    • 将验证添加到电影应用
    • ASP.NET Core 中的模型验证.
    • 使用 OnGet 处理程序回退来处理 HEAD 请求

      HEAD 请求可检索特定资源的标头。 与 GET 请求不同,HEAD 请求不返回响应正文。

      通常,针对 HEAD 请求创建和调用 OnHead 处理程序:

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

      如果未定义 OnHead 处理程序,则 Razor Pages 会回退到调用 OnGet 处理程序。

      XSRF/CSRF 和 Razor Pages

      Razor Pages 由 防伪造验证保护。 FormTagHelper 将防伪造令牌注入 HTML 窗体元素。

      将布局、分区、模板和标记帮助程序用于 Razor Pages

      页面可使用 Razor 视图引擎的所有功能。 布局、分区、模板、标记帮助程序、_ViewStart.cshtml_ViewImports.cshtml 的工作方式与它们在传统的 Razor 视图中的工作方式相同。

      让我们使用其中的一些功能来整理此页面。

      Pages/Shared/_Layout.cshtml 添加布局页面

      <!DOCTYPE html>
          <title>RP Sample</title>
          <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
      </head>
          <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>
      
    • 控制每个页面的布局(页面选择退出布局时除外)。
    • 导入 HTML 结构,例如 JavaScript 和样式表。
    • 调用 @RenderBody() 时,呈现 Razor Page 的内容。
    • 有关详细信息,请参阅布局页面

      Pages/_ViewStart.cshtml 中设置 Layout 属性:

      Layout = "_Layout";

      布局位于“页面/共享”文件夹中。 页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。 可以从“Pages”文件夹下的任意 Razor 页面使用“Pages/Shared”文件夹中的布局。

      布局文件应位于 Pages/Shared 文件夹中。

      建议不要将布局文件放在“视图/共享”文件夹中。 视图/共享 是一种 MVC 视图模式。 Razor Pages 旨在依赖文件夹层次结构,而非路径约定。

      Razor Page 中的视图搜索包含“Pages”文件夹。 用于 MVC 控制器和传统 Razor 视图的布局、模板和分区可正常运行。

      添加 Pages/_ViewImports.cshtml 文件:

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

      本教程的后续部分中将介绍 @namespace@addTagHelper 指令将内置标记帮助程序引入“页面”文件夹中的所有页面。

      页面上设置的 @namespace 指令:

      @page
      @namespace RazorPagesIntro.Pages.Customers
      @model NameSpaceModel
      <h2>Name space</h2>
          @Model.Message
      

      @namespace 指令将为页面设置命名空间。 @model 指令无需包含命名空间。

      _ViewImports.cshtml 中包含 @namespace 指令后,指定的命名空间将为在导入 @namespace 指令的页面中生成的命名空间提供前缀。 生成的命名空间的剩余部分(后缀部分)是包含 _ViewImports.cshtml 的文件夹与包含页面的文件夹之间以点分隔的相对路径。

      例如,PageModelPages/Customers/Edit.cshtml.cs 显式设置命名空间:

      namespace RazorPagesContacts.Pages
          public class EditModel : PageModel
              private readonly AppDbContext _db;
              public EditModel(AppDbContext db)
                  _db = db;
              // Code removed for brevity.
      

      Pages/_ViewImports.cshtml 文件设置以下命名空间:

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

      Pages/Customers/Edit.cshtmlRazor Page 生成的命名空间与 PageModel 类相同。

      @namespace 也适用于传统 Razor 视图。

      考虑 Pages/Create.cshtml 视图文件:

      @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 视图文件和前面的布局文件:

      @page
      @model CreateModel
      <p>Enter a customer name:</p>
      <form method="post">
          Name:
          <input asp-for="Customer.Name" />
          <input type="submit" />
      </form>
      

      在前面的代码中,_ViewImports.cshtml 导入了命名空间和标记帮助程序。 布局文件导入了 JavaScript 文件。

      Razor Pages 初学者项目包含 Pages/_ValidationScriptsPartial.cshtml,它与客户端验证联合。

      有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图

      页面的 URL 生成

      之前显示的 Create 页面使用 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");
      

      应用具有以下文件/文件夹结构:

    • /Pages

    • Index.cshtml

    • Privacy.cshtml

    • /Customers

    • Create.cshtml
    • Edit.cshtml
    • Index.cshtml
    • 成功后,Pages/Customers/Create.cshtmlPages/Customers/Edit.cshtml 页面将重定向到 Pages/Customers/Index.cshtml。 字符串 ./Index 是用于访问前一页的相对页名称。 它用于生成 Pages/Customers/Index.cshtml 页面的 URL。 例如:

    • Url.Page("./Index", ...)
    • <a asp-page="./Index">Customers Index Page</a>
    • RedirectToPage("./Index")
    • 绝对页名称 /Index 用于生成 Pages/Index.cshtml 页面的 URL。 例如:

    • Url.Page("/Index", ...)
    • <a asp-page="/Index">Home Index Page</a>
    • RedirectToPage("/Index")
    • 页面名称是从根“/Pages”文件夹到页面的路径(包含前导 /,例如 /Index)。 与硬编码 URL 相比,前面的 URL 生成示例提供了改进的选项和功能。 URL 生成使用路由,并且可以根据目标路径定义路由的方式生成参数并对参数编码。

      页面的 URL 生成支持相对名称。 下表显示了 Pages/Customers/Create.cshtml 中不同的 RedirectToPage 参数选择的索引页。

      RedirectToPage(x)

      RedirectToPage("Index")RedirectToPage("./Index")RedirectToPage("../Index") 是相对名称。 结合RedirectToPage 参数与当前页的路径来计算目标页面的名称。

      构建结构复杂的站点时,相对名称链接很有用。 如果使用相对名称链接文件夹中的页面:

    • 重命名文件夹不会破坏相对链接。
    • 链接不会中断,因为它们不包含文件夹名称。
    • 若要重定向到不同区域中的页面,请指定区域:

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

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

      ViewData 特性

      可以通过 ViewDataAttribute 将数据传递到页面。 具有 [ViewData] 特性的属性从 ViewDataDictionary 保存和加载值。

      在下面的示例中,AboutModel[ViewData] 特性应用于 Title 属性:

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

      在“关于”页面中,以模型属性的形式访问 Title 属性:

      <h1>@Model.Title</h1>
      

      在布局中,从 ViewData 字典读取标题:

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

      TempData

      ASP.NET Core 公开 TempData。 此属性存储未读取的数据。 KeepPeek 方法可用于检查数据,而不执行删除。 TempData 在多个请求需要数据的情况下对重定向很有用。

      下面的代码使用 TempData 设置 Message 的值:

      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 的值。

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

      Pages/Customers/Index.cshtml.cs 页面模型将 [TempData] 属性应用到 Message 属性。

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

      有关详细信息,请参阅 TempData

      针对一个页面的多个处理程序

      以下页面使用 asp-page-handler 标记帮助程序为两个处理程序生成标记:

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

      前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper 提交到不同的 URL。 asp-page-handlerasp-page 的配套属性。 asp-page-handler 生成提交到页面定义的各个处理程序方法的 URL。 未指定 asp-page,因为示例已链接到当前页面。

      页面模型:

      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();
      

      前面的代码使用已命名处理程序方法。 已命名处理程序方法通过采用名称中 On<HTTP Verb> 之后及 Async 之前的文本(如果有)创建。 在前面的示例中,页面方法是 OnPostJoinListAsync 和 OnPostJoinListUCAsync。 删除 OnPost 和 Async 后,处理程序名称为 JoinListJoinListUC

      <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=JoinList。 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH?handler=JoinListUC

      自定义路由

      使用 @page 指令,执行以下操作:

    • 指定页面的自定义路由。 例如,使用 @page "/Some/Other/Path" 将“关于”页面的路由设置为 /Some/Other/Path
    • 将段追加到页面的默认路由。 例如,可使用 @page "item" 将“item”段添加到页面的默认路由。
    • 将参数追加到页面的默认路由。 例如,@page "{id}" 页面需要 ID 参数 id
    • 支持开头处以波形符 (~) 指定的相对于根目录的路径。 例如,@page "~/Some/Other/Path"@page "/Some/Other/Path" 相同。

      如果你不喜欢 URL 中的查询字符串 ?handler=JoinList,请更改路由,将处理程序名称放在 URL 的路径部分。 可以通过在 @page 指令后面添加使用双引号括起来的路由模板来自定义路由。

      @page "{handler?}"
      @model CreateRouteModel
              Enter your name.
          <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/JoinList。 提交到 OnPostJoinListUCAsync 的 URL 路径为 https://localhost:5001/Customers/CreateFATH/JoinListUC

      handler 前面的 ? 表示路由参数为可选。

      高级配置和设置

      大多数应用不需要以下部分中的配置和设置。

      要配置高级选项,请使用 AddRazorPages 重载,该重载配置 RazorPagesOptions

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

      使用 RazorPagesOptions 设置页面的根目录,或者为页面添加应用程序模型约定。 有关约定的详细信息,请参阅 Razor Pages 授权约定.

      若要预编译视图,请参阅 Razor 视图编译

      指定 Razor Pages 位于内容根目录中

      默认情况下,Razor Pages 位于 /Pages 目录的根位置。 添加 WithRazorPagesAtContentRoot 以指定 Razor Pages 位于应用的内容根 (ContentRootPath):

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

      指定 Razor Pages 位于自定义根目录中

      添加 WithRazorPagesRoot,以指定 Razor Pages 位于应用中自定义根目录位置(提供相对路径):

      public void ConfigureServices(IServiceCollection services)
          services.AddRazorPages(options =>
                  options.Conventions.AuthorizeFolder("/MyPages/Admin");
              .WithRazorPagesRoot("/path/to/razor/pages");
      
    • 请参阅 Razor Pages 入门这篇文章以本文为基础撰写。
    • 授权属性和 Razor Pages
    • 下载或查看示例代码
    • ASP.NET Core 概述
    • ASP.NET Core 的 Razor 语法参考
    • ASP.NET Core 中的区域
    • 教程:在 ASP.NET Core 中开始使用 Razor Pages
    • Razor ASP.NET Core 中的 Pages 授权约定
    • Razor ASP.NET Core 中的 Pages 路由和应用约定
    • Razor ASP.NET Core 中的 Pages 单元测试
    • ASP.NET Core 中的分部视图
    • 预呈现和集成 ASP.NET Core Razor 组件
  •