ASP.NET Core 中的 Razor Pages 介绍
作者:Rick Anderson、Dave Brock 和 Kirk Larkin
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
通过 Razor Pages 对基于页面的场景编码比使用控制器和视图更轻松、更高效。
若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门。
本文档介绍 Razor Pages。 它并不是分步教程。 如果认为某些部分过于复杂,请参阅 Razor Pages 入门。 有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介。
先决条件
- 带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022。
- .NET 6.0 SDK
创建 Razor Pages 项目
请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。
Razor Pages
Program.cs
中已启用 Razor Pages:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
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();
在上述代码中:
- 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>
<p>
@Model.Message
</p>
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 |
---|---|
/Pages/Index.cshtml |
/ 或 /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store 或 /Store/Index |
注意:
- 默认情况下,运行时在“Pages”文件夹中查找 Razor Pages 文件。
- URL 未包含页面时,
Index
为默认页面。
编写基本窗体
由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。 模型绑定、标记帮助程序和 HTML 帮助程序可使用 Razor Page 类中定义的属性。 请参考为 Contact
模型实现的基本的“联系我们”窗体页面:
在本文档中的示例中,DbContext
在 Program.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
方法显示Create.cshtml
Razor Page。OnPost
,用于处理窗体提交。
Async
命名后缀为可选,但是按照惯例通常会将它用于异步函数。 前面的代码通常用于 Razor Pages。
如果你熟悉使用控制器和视图的 ASP.NET 应用:
之前的 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
:- 是操作结果。
- 类似于
RedirectToAction
或RedirectToRoute
(用于控制器和视图)。 - 针对页面自定义。 在前面的示例中,它将重定向到根索引页 (
/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]
特性来选择加入模型绑定:
[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
提供标记帮助程序。
home 页
Index.cshtml
是 home 页:
@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>
<th></th>
</tr>
</thead>
<tbody>
@if (Model.Customers != null)
{
foreach (var contact in Model.Customers)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<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> -->
</td>
</tr>
}
}
</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&handler=delete">delete</button>
删除按钮采用 HTML 呈现,其 formaction 包括参数:
asp-route-id
属性指定的客户联系人 ID。asp-page-handler
属性指定的handler
。
选中按钮时,向服务器发送窗体 POST
请求。 按照惯例,根据方案 OnPost[handler]Async
基于 handler
参数的值来选择处理程序方法的名称。
因为本示例中 handler
是 delete
,因此 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>
<hr />
<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>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="./Index">Back to List</a>
</div>
@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;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!CustomerExists(Customer.Id))
{
return NotFound();
}
else
{
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; }
}
}
验证特性指定要对应用这些特性的模型属性强制执行的行为:
Required
和MinimumLength
特性表示属性必须有值,但用户可输入空格来满足此验证。RegularExpression
特性用于限制可输入的字符。 在上述代码中,即“Genre”(分类):- 只能使用字母。
- 第一个字母必须为大写。 不允许使用空格、数字和特殊字符。
RegularExpression
“Rating”(分级):- 要求第一个字符为大写字母。
- 允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“分类”无效。
Range
特性将值限制在指定范围内。StringLength
特性可以设置字符串属性的最大长度,以及可选的最小长度。从本质上来说,需要值类型(如
decimal
、int
、float
、DateTime
),但不需要[Required]
特性。
Movie
模型的“创建”页面显示无效值错误:
有关详细信息,请参阅:
CSS 隔离
将 CSS 样式隔离到各个页面、视图和组件以减少或避免:
- 依赖难以维护的全局样式。
- 嵌套内容中的样式冲突。
若要为页面或视图添加限定范围的 CSS 文件,请将 CSS 样式置于与 .cshtml
文件的名称匹配的配套 .cshtml.css
文件中。 在下面的示例中,Index.cshtml.css
文件提供只应用于 Index.cshtml
页面或视图的 CSS 样式。
Pages/Index.cshtml.css
(Razor Pages) 或 Views/Index.cshtml.css
(MVC):
h1 {
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 组件都与格式为 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 文件和应用资产的 rest 放在 _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 的详细信息,请参阅以下文章:
有关 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>
<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>
布局:
- 控制每个页面的布局(页面选择退出布局时除外)。
- 导入 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>
<p>
@Model.Message
</p>
@namespace
指令将为页面设置命名空间。 @model
指令无需包含命名空间。
_ViewImports.cshtml
中包含 @namespace
指令后,指定的命名空间将为在导入 @namespace
指令的页面中生成的命名空间提供前缀。 生成的命名空间中的 rest(后缀部分)是包含 _ViewImports.cshtml
的文件夹和包含页面的文件夹之间以点分隔的相对路径。
例如,PageModel
类 Pages/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.cshtml
Razor 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 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");
}
}
应用具有以下文件/文件夹结构:
/Pages
Index.cshtml
Privacy.cshtml
/Customers
Create.cshtml
Edit.cshtml
Index.cshtml
成功后,Pages/Customers/Create.cshtml
和 Pages/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") | Pages/Index |
RedirectToPage("./Index"); | Pages/Customers/Index |
RedirectToPage("../Index") | Pages/Index |
RedirectToPage("Index") | Pages/Customers/Index |
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">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
TempData
ASP.NET Core 公开 TempData。 此属性存储未读取的数据。 Keep 和 Peek 方法可用于检查数据,而不执行删除。 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
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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-handler
是 asp-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 后,处理程序名称为 JoinList
和 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=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
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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) 文件的并置
为页面和视图并置 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
。
- 对于位于
使用项目中文件的路径可以公开寻址并置的 JS 文件:
应用中并置脚本文件的页面和视图:
{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
- 占位符
{PATH}
是页面、视图或组件的路径。 - 占位符
{PAGE, VIEW, OR COMPONENT}
是页面、视图或组件。 - 占位符
{EXTENSION}
与页面、视图或组件的扩展名匹配,为razor
或cshtml
。
Razor Pages 示例:
Index
页面的 JS 文件放置在Index
页面 (Pages/Index.cshtml
) 旁边的Pages
文件夹 (Pages/Index.cshtml.js
) 中。 在Index
页面中,脚本在Pages
文件夹中的路径引用:@section Scripts { <script src="~/Pages/Index.cshtml.js"></script> }
- 占位符
可以将默认布局 Pages/Shared/_Layout.cshtml
配置为包含并置 JS 文件,无需单独配置每个页面:
<script asp-src-include="@(ViewContext.View.Path).js"></script>
示例下载使用前面的代码片段在默认布局中包含并置 JS 文件。
发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本将移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Pages\Index.cshtml.js
,其中 {TARGET FRAMEWORK MONIKER}
占位符是目标框架名字对象 (TFM)。 无需更改 Index
页面中脚本的相对 URL。
发布应用后,框架会自动将脚本移动到 Web 根目录。 在前面的示例中,脚本被移动到 bin\Release\{TARGET FRAMEWORK MONIKER}\publish\wwwroot\Components\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}
与页面、视图或组件的扩展名匹配,为razor
或cshtml
。
- 占位符
高级配置和设置
大多数应用不需要以下部分中的配置和设置。
要配置高级选项,请使用 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();
其他资源
- 具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2019 16.4 或更高版本
- .NET Core 3.1 SDK
- 具有“ASP.NET 和 Web 开发”工作负载的 Visual Studio 2019 16.8 或更高版本
- .NET 5.0 SDK
创建 Razor Pages 项目
请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。
Razor Pages
Startup.cs
中已启用 Razor Pages:
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();
});
}
}
@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>
<p>
@Model.Message
</p>
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 |
---|---|
/Pages/Index.cshtml |
/ 或 /Index |
/Pages/Contact.cshtml |
/Contact |
/Pages/Store/Contact.cshtml |
/Store/Contact |
/Pages/Store/Index.cshtml |
/Store 或 /Store/Index |
注意:
- 默认情况下,运行时在“Pages”文件夹中查找 Razor Pages 文件。
- URL 未包含页面时,
Index
为默认页面。
编写基本窗体
由于 Razor Pages 的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。 模型绑定、标记帮助程序和 HTML 帮助程序均只可用于 Razor Page 类中定义的属性。 请参考为 Contact
模型实现的基本的“联系我们”窗体页面:
在本文档中的示例中,DbContext
在 Startup.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.cshtml
Razor Page。OnPost
,用于处理窗体提交。
Async
命名后缀为可选,但是按照惯例通常会将它用于异步函数。 前面的代码通常用于 Razor Pages。
如果你熟悉使用控制器和视图的 ASP.NET 应用:
之前的 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
:- 是操作结果。
- 类似于
RedirectToAction
或RedirectToRoute
(用于控制器和视图)。 - 针对页面自定义。 在前面的示例中,它将重定向到根索引页 (
/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
提供标记帮助程序。
home 页
Index.cshtml
是 home 页:
@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>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var contact in Model.Customer)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<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> -->
</td>
</tr>
}
</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&handler=delete">delete</button>
删除按钮采用 HTML 呈现,其 formaction 包括参数:
asp-route-id
属性指定的客户联系人 ID。asp-page-handler
属性指定的handler
。
选中按钮时,向服务器发送窗体 POST
请求。 按照惯例,根据方案 OnPost[handler]Async
基于 handler
参数的值来选择处理程序方法的名称。
因为本示例中 handler
是 delete
,因此 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" />
<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}"
指令。 路由约束 "{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;
try
{
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; }
}
}
验证特性指定要对应用这些特性的模型属性强制执行的行为:
Required
和MinimumLength
特性表示属性必须有值,但用户可输入空格来满足此验证。RegularExpression
特性用于限制可输入的字符。 在上述代码中,即“Genre”(分类):- 只能使用字母。
- 第一个字母必须为大写。 不允许使用空格、数字和特殊字符。
RegularExpression
“Rating”(分级):- 要求第一个字符为大写字母。
- 允许在后续空格中使用特殊字符和数字。 “PG-13”对“分级”有效,但对于“分类”无效。
Range
特性将值限制在指定范围内。StringLength
特性可以设置字符串属性的最大长度,以及可选的最小长度。从本质上来说,需要值类型(如
decimal
、int
、float
、DateTime
),但不需要[Required]
特性。
Movie
模型的“创建”页面显示无效值错误:
有关详细信息,请参阅:
使用 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>
<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>
布局:
- 控制每个页面的布局(页面选择退出布局时除外)。
- 导入 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>
<p>
@Model.Message
</p>
@namespace
指令将为页面设置命名空间。 @model
指令无需包含命名空间。
_ViewImports.cshtml
中包含 @namespace
指令后,指定的命名空间将为在导入 @namespace
指令的页面中生成的命名空间提供前缀。 生成的命名空间中的 rest(后缀部分)是包含 _ViewImports.cshtml
的文件夹和包含页面的文件夹之间以点分隔的相对路径。
例如,PageModel
类 Pages/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.cshtml
Razor 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.cshtml
和 Pages/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") | Pages/Index |
RedirectToPage("./Index"); | Pages/Customers/Index |
RedirectToPage("../Index") | Pages/Index |
RedirectToPage("Index") | Pages/Customers/Index |
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">
<head>
<title>@ViewData["Title"] - WebApplication</title>
...
TempData
ASP.NET Core 公开 TempData。 此属性存储未读取的数据。 Keep 和 Peek 方法可用于检查数据,而不执行删除。 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
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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-handler
是 asp-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 后,处理程序名称为 JoinList
和 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=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
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div><label>Name: <input asp-for="Customer.Name" /></label></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");
}