- ASP.NET Core 中的 Razor 页面介绍Introduction to Razor Pages in ASP.NET Core
- 先决条件Prerequisites
- 创建 Razor Pages 项目Create a Razor Pages project
- Razor 页面Razor Pages
- 编写基本窗体Write a basic form
- 验证Validation
- 使用 OnGet 处理程序回退来处理 HEAD 请求Handle HEAD requests with an OnGet handler fallback
- XSRF/CSRF 和 Razor 页面XSRF/CSRF and Razor Pages
- 将布局、分区、模板和标记帮助程序用于 Razor 页面Using Layouts, partials, templates, and Tag Helpers with Razor Pages
- 页面的 URL 生成URL generation for Pages
- ViewData 特性ViewData attribute
- TempDataTempData
- 针对一个页面的多个处理程序Multiple handlers per page
- 自定义路由Custom routes
- 高级配置和设置Advanced configuration and settings
- 其他资源Additional resources
- 先决条件Prerequisites
- 创建 Razor Pages 项目Create a Razor Pages project
- Razor 页面Razor Pages
- 编写基本窗体Write a basic form
- 按需标记页面属性Mark page properties as required
- 使用 OnGet 处理程序回退来处理 HEAD 请求Handle HEAD requests with an OnGet handler fallback
- XSRF/CSRF 和 Razor 页面XSRF/CSRF and Razor Pages
- 将布局、分区、模板和标记帮助程序用于 Razor 页面Using Layouts, partials, templates, and Tag Helpers with Razor Pages
- 页面的 URL 生成URL generation for Pages
- ViewData 特性ViewData attribute
- TempDataTempData
- 针对一个页面的多个处理程序Multiple handlers per page
- 自定义路由Custom routes
- 配置和设置Configuration and settings
- 其他资源Additional resources
ASP.NET Core 中的 Razor 页面介绍Introduction to Razor Pages in ASP.NET Core
本文内容
作者:Rick Anderson 和 Ryan Nowak
通过 Razor Pages 对基于页面的场景编码比使用控制器和视图更轻松、更高效。
若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门。
本文档介绍 Razor 页面。它并不是分步教程。如果认为某些部分过于复杂,请参阅 Razor 页面入门。有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介。
先决条件Prerequisites
- 带有 ASP.NET 和 Web 开发工作负荷的 Visual Studio 2019
- .NET Core 3.0 SDK 或更高版本
Visual Studio Code 说明使用用于 ASP.NET Core 的 .NET Core CLI 开发功能,如项目创建。可在任何平台(macOS、Linux 或 Windows)上或在任何代码编辑器中遵循这些说明。如果使用 Visual Studio Code 以外的其他内容,则可能需要进行少量更改。
创建 Razor Pages 项目Create a Razor Pages project
请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。
在命令行中运行 dotnet new webapp
。
在命令行中运行 dotnet new webapp
。
在 Visual Studio for Mac 中打开生成的 .csproj 文件。
Razor 页面Razor Pages
Startup.cs 中已启用 Razor 页面 :
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 页面文件名称相同。例如,前面的 Razor 页面的名称为 Pages/Index2.cshtml 。包含 PageModel
类的文件的名称为 Pages/Index2.cshtml.cs 。
页面的 URL 路径的关联由页面在文件系统中的位置决定。下表显示了 Razor 页面路径及匹配的 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 页面文件。
- URL 未包含页面时,
Index
为默认页面。
编写基本窗体Write a basic form
由于 Razor 页面的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。模型绑定、标记帮助程序和 HTML 帮助程序均只可用于 Razor 页面类中定义的属性。 请参考为 Contact
模型实现基本的“联系我们”窗体的页面:
在本文档中的示例中,DbContext
在 Startup.cs 文件中进行初始化。
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 页面。
如果你熟悉使用控制器和视图的 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 页面只绑定带有非 GET
谓词的属性。如果绑定到属性,则无需通过编写代码将 HTTP 数据转换为模型类型。绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">
) 来减少代码,并接受输入。
警告
出于安全原因,必须选择绑定 GET
请求数据以对模型属性进行分页。请在将用户输入映射到属性前对其进行验证。当处理依赖查询字符串或路由值的方案时,选择加入 GET
绑定非常有用。
若要将属性绑定在 GET
请求上,请将 [BindProperty]
特性的 SupportsGet
属性设置为 true
:
[BindProperty(SupportsGet = true)]
有关详细信息,请参阅 ASP.NET Core Community Standup:Bind on GET discussion (YouTube)(绑定 GET 讨论)。
查看 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
提供标记帮助程序。
主页The home page
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>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@foreach (var contact in Model.Customer)
{
<tr>
<td> @contact.Id </td>
<td>@contact.Name</td>
<td>
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a> |
<button type="submit" asp-page-handler="delete"
asp-route-id="@contact.Id">delete
</button>
</td>
</tr>
}
</tbody>
</table>
<a asp-page="Create">Create New</a>
</form>
关联的 PageModel
类 (Index.cshtml.cs) :
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
呈现的 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 文件The Edit.cshtml file
@page "{id:int}"
@model RazorPagesContacts.Pages.Customers.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
<div asp-validation-summary="All"></div>
<input asp-for="Customer.Id" type="hidden" />
<div>
<label asp-for="Customer.Name"></label>
<div>
<input asp-for="Customer.Name" />
<span asp-validation-for="Customer.Name"></span>
</div>
</div>
<div>
<button type="submit">Save</button>
</div>
</form>
第一行包含 @page "{id:int}"
指令。路由约束 "{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");
}
}
验证Validation
验证规则:
- 在模型类中以声明方式指定。
- 在应用中的所有位置强制执行。
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
模型:
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 请求Handle HEAD requests with an OnGet handler fallback
HEAD
请求可检索特定资源的标头。与 GET
请求不同,HEAD
请求不返回响应正文。
通常,针对 HEAD
请求创建和调用 OnHead
处理程序:
public void OnHead()
{
HttpContext.Response.Headers.Add("Head Test", "Handled by OnHead!");
}
如果未定义 OnHead
处理程序,则 Razor Pages 会回退到调用 OnGet
处理程序。
XSRF/CSRF 和 Razor 页面XSRF/CSRF and Razor Pages
Razor Pages 由防伪造验证保护。FormTagHelper 将防伪造令牌注入 HTML 窗体元素。
将布局、分区、模板和标记帮助程序用于 Razor 页面Using Layouts, partials, templates, and Tag Helpers with 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";
}
布局位于“页面/共享”文件夹中。页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。可以从“页面/共享” 文件夹下的任意 Razor 页面使用“页面” 文件夹中的布局。
布局文件应位于 Pages/Shared 文件夹中。
建议不要 将布局文件放在“视图/共享” 文件夹中。视图/共享 是一种 MVC 视图模式。Razor 页面旨在依赖文件夹层次结构,而非路径约定。
Razor 页面中的视图搜索包含“页面” 文件夹。用于 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
指令的页面中生成的命名空间提供前缀。生成的命名空间的剩余部分(后缀部分)是包含 _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 页面生成的命名空间与 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/_ValidationScriptsPartial.cshtml ,它与客户端验证联合。
有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图。
页面的 URL 生成URL generation for Pages
之前显示的 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 and 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 页面的路由和应用约定。
ViewData 特性ViewData attribute
可以通过 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>
...
TempDataTempData
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。
针对一个页面的多个处理程序Multiple handlers per page
以下页面使用 asp-page-handler
标记帮助程序为两个处理程序生成标记:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper
提交到不同的 URL。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
。
自定义路由Custom routes
使用 @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>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
前面的 ?
表示路由参数为可选。
高级配置和设置Advanced configuration and settings
大多数应用不需要以下部分中的配置和设置。
要配置高级选项,请使用扩展方法 AddRazorPagesOptions:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
}
使用 RazorPagesOptions 设置页面的根目录,或者为页面添加应用程序模型约定。有关约定的详细信息,请参阅 Razor Pages 约定。
若要预编译视图,请参阅 Razor 视图编译。
指定 Razor 页面位于内容根目录中Specify that Razor Pages are at the content root
默认情况下,Razor 页面位于 /Pages 目录的根位置 。添加 WithRazorPagesAtContentRoot 以指定 Razor Pages 位于应用的内容根 (ContentRootPath) 中:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesAtContentRoot();
}
指定 Razor 页面位于自定义根目录中Specify that Razor Pages are at a custom root directory
添加 WithRazorPagesRoot,以指定 Razor 页面位于应用中自定义根目录位置(提供相对路径):
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/MyPages/Admin");
})
.WithRazorPagesRoot("/path/to/razor/pages");
}
其他资源Additional resources
- 请参阅 Razor Pages 入门这篇文章以本文为基础撰写
- 下载或查看示例代码
- ASP.NET Core 简介
- ASP.NET Core 的 Razor 语法参考
- ASP.NET Core 中的区域
- 教程:开始使用ASP.NET Core中的Razor Pages
- 在 ASP.NET Core razor 页授权约定
- ASP.NET Core 中 Razor 页面的路由和应用约定
- ASP.NET Core 中的 Razor Pages 单元测试
- ASP.NET Core 中的分部视图
- 将 ASP.NET Core Razor 组件集成到 Razor Pages 和 MVC 应用
作者:Rick Anderson 和 Ryan Nowak
Razor 页面是 ASP.NET Core MVC 的一个新特性,它可以使基于页面的编码方式更简单高效。
若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门。
本文档介绍 Razor 页面。它并不是分步教程。如果认为某些部分过于复杂,请参阅 Razor 页面入门。有关 ASP.NET Core 的概述,请参阅 ASP.NET Core 简介。
先决条件Prerequisites
- Visual Studio 2019 与 ASP.NET 和 Web 开发 工作负载
- .NET Core SDK 2.2 或更高版本
警告
如果使用 Visual Studio 2017,请参阅 dotnet/sdk 问题 #3124,以了解无法与 Visual Studio 一起使用的 .NET Core SDK 版本的信息。
Visual Studio Code 说明使用用于 ASP.NET Core 的 .NET Core CLI 开发功能,如项目创建。可在任何平台(macOS、Linux 或 Windows)上或在任何代码编辑器中遵循这些说明。如果使用 Visual Studio Code 以外的其他内容,则可能需要进行少量更改。
创建 Razor Pages 项目Create a Razor Pages project
请参阅 Razor Pages 入门,获取关于如何创建 Razor Pages 项目的详细说明。
在命令行中运行 dotnet new webapp
。
在 Visual Studio for Mac 中打开生成的 .csproj 文件。
在命令行中运行 dotnet new webapp
。
Razor 页面Razor Pages
Startup.cs 中已启用 Razor 页面 :
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Includes support for Razor Pages and controllers.
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
@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 构造的行为。
将在以下两个文件中显示使用 PageModel
类的类似页面。Pages/Index2.cshtml 文件:
@page
@using RazorPagesIntro.Pages
@model IndexModel2
<h2>Separate page model</h2>
<p>
@Model.Message
</p>
Pages/Index2.cshtml.cs 页面模型 :
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
namespace RazorPagesIntro.Pages
{
public class IndexModel2 : PageModel
{
public string Message { get; private set; } = "PageModel in C#";
public void OnGet()
{
Message += $" Server time is { DateTime.Now }";
}
}
}
按照惯例,PageModel
类文件的名称与追加 .cs 的 Razor 页面文件名称相同。例如,前面的 Razor 页面的名称为 Pages/Index2.cshtml 。包含 PageModel
类的文件的名称为 Pages/Index2.cshtml.cs 。
页面的 URL 路径的关联由页面在文件系统中的位置决定。下表显示了 Razor 页面路径及匹配的 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 页面文件。
- URL 未包含页面时,
Index
为默认页面。
编写基本窗体Write a basic form
由于 Razor 页面的设计,在构建应用时可轻松实施用于 Web 浏览器的常用模式。模型绑定、标记帮助程序和 HTML 帮助程序均只可用于 Razor 页面类中定义的属性。 请参考为 Contact
模型实现基本的“联系我们”窗体的页面:
在本文档中的示例中,DbContext
在 Startup.cs 文件中进行初始化。
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using RazorPagesContacts.Data;
namespace RazorPagesContacts
{
public class Startup
{
public IHostingEnvironment HostingEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("name"));
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc();
}
}
}
数据模型:
using System.ComponentModel.DataAnnotations;
namespace RazorPagesContacts.Data
{
public class Customer
{
public int Id { get; set; }
[Required, StringLength(100)]
public string Name { get; set; }
}
}
数据库上下文:
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Data
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions options)
: base(options)
{
}
public DbSet<Customer> Customers { get; set; }
}
}
Pages/Create.cshtml 视图文件:
@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>
Pages/Create.cshtml.cs 页面模型 :
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
namespace RazorPagesContacts.Pages
{
public class CreateModel : PageModel
{
private readonly AppDbContext _db;
public CreateModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}
}
按照惯例,PageModel
类命名为 <PageName>Model
并且它与页面位于同一个命名空间中。
使用 PageModel
类,可以将页面的逻辑与其展示分离开来。它定义了页面处理程序,用于处理发送到页面的请求和用于呈现页面的数据。这种隔离可实现:
页面包含 OnPostAsync
处理程序方法 ,它在 POST
请求上运行(当用户发布窗体时)。可以为任何 HTTP 谓词添加处理程序方法。最常见的处理程序是:
OnGet
,用于初始化页面所需的状态。OnGet 示例。OnPost
,用于处理窗体提交。
Async
命名后缀为可选,但是按照惯例通常会将它用于异步函数。前面的代码通常用于 Razor 页面。
如果你熟悉使用控制器和视图的 ASP.NET 应用:
之前的 OnPostAsync
方法:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
OnPostAsync
的基本流:
检查验证错误。
- 如果没有错误,则保存数据并重定向。
- 如果有错误,则再次显示页面并附带验证消息。客户端验证与传统的 ASP.NET Core MVC 应用程序相同。很多情况下,都会在客户端上检测到验证错误,并且从不将它们提交到服务器。
成功输入数据后,OnPostAsync
处理程序方法调用 RedirectToPage
帮助程序方法来返回 RedirectToPageResult
的实例。RedirectToPage
是新的操作结果,类似于 RedirectToAction
或 RedirectToRoute
,但是已针对页面进行自定义。在前面的示例中,它将重定向到根索引页 (/Index
)。页面 URL 生成部分中详细介绍了 RedirectToPage
。
提交的窗体存在(已传递到服务器的)验证错误时,OnPostAsync
处理程序方法调用 Page
帮助程序方法。Page
返回 PageResult
的实例。返回 Page
的过程与控制器中的操作返回 View
的过程相似。PageResult
是处理程序方法的默认返回类型。返回 void
的处理程序方法将显示页面。
Customer
属性使用 [BindProperty]
特性来选择加入模型绑定。
public class CreateModel : PageModel
{
private readonly AppDbContext _db;
public CreateModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
}
默认情况下,Razor 页面只绑定带有非 GET
谓词的属性。绑定属性可以减少需要编写的代码量。绑定通过使用相同的属性显示窗体字段 (<input asp-for="Customer.Name">
) 来减少代码,并接受输入。
警告
出于安全原因,必须选择绑定 GET
请求数据以对模型属性进行分页。请在将用户输入映射到属性前对其进行验证。当处理依赖查询字符串或路由值的方案时,选择加入 GET
绑定非常有用。
若要将属性绑定在 GET
请求上,请将 [BindProperty]
特性的 SupportsGet
属性设置为 true
:
[BindProperty(SupportsGet = true)]
有关详细信息,请参阅 ASP.NET Core Community Standup:Bind on GET discussion (YouTube)(绑定 GET 讨论)。
主页 (Index.cshtml ):
@page
@model RazorPagesContacts.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<h1>Contacts</h1>
<form method="post">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
@foreach (var contact in Model.Customers)
{
<tr>
<td>@contact.Id</td>
<td>@contact.Name</td>
<td>
<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
<button type="submit" asp-page-handler="delete"
asp-route-id="@contact.Id">delete</button>
</td>
</tr>
}
</tbody>
</table>
<a asp-page="./Create">Create</a>
</form>
关联的 PageModel
类 (Index.cshtml.cs) :
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesContacts.Data;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
namespace RazorPagesContacts.Pages
{
public class IndexModel : PageModel
{
private readonly AppDbContext _db;
public IndexModel(AppDbContext db)
{
_db = db;
}
public IList<Customer> Customers { get; private set; }
public async Task OnGetAsync()
{
Customers = await _db.Customers.AsNoTracking().ToListAsync();
}
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _db.Customers.FindAsync(id);
if (contact != null)
{
_db.Customers.Remove(contact);
await _db.SaveChangesAsync();
}
return RedirectToPage();
}
}
}
Index.cshtml 文件包含以下标记来创建每个联系人项的编辑链接:
<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a>
<a asp-page="./Edit" asp-route-id="@contact.Id">Edit</a>
定位点标记帮助程序使用 asp-route-{value}
属性生成“编辑”页面的链接。此链接包含路由数据及联系人 ID。例如 https://localhost:5001/Edit/1
。标记帮助程序使服务器端代码可以在 Razor 文件中参与创建和呈现 HTML 元素。标记帮助程序由 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
启动
Pages/Edit.cshtml 文件:
@page "{id:int}"
@model RazorPagesContacts.Pages.EditModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
ViewData["Title"] = "Edit Customer";
}
<h1>Edit Customer - @Model.Customer.Id</h1>
<form method="post">
<div asp-validation-summary="All"></div>
<input asp-for="Customer.Id" type="hidden" />
<div>
<label asp-for="Customer.Name"></label>
<div>
<input asp-for="Customer.Name" />
<span asp-validation-for="Customer.Name" ></span>
</div>
</div>
<div>
<button type="submit">Save</button>
</div>
</form>
第一行包含 @page "{id:int}"
指令。路由约束 "{id:int}"
告诉页面接受包含 int
路由数据的页面请求。如果页面请求未包含可转换为 int
的路由数据,则运行时返回 HTTP 404(未找到)错误。若要使 ID 可选,请将 ?
追加到路由约束:
@page "{id:int?}"
Pages/Edit.cshtml.cs 文件:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using RazorPagesContacts.Data;
namespace RazorPagesContacts.Pages
{
public class EditModel : PageModel
{
private readonly AppDbContext _db;
public EditModel(AppDbContext db)
{
_db = db;
}
[BindProperty]
public Customer Customer { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Customer = await _db.Customers.FindAsync(id);
if (Customer == null)
{
return RedirectToPage("/Index");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Attach(Customer).State = EntityState.Modified;
try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new Exception($"Customer {Customer.Id} not found!");
}
return RedirectToPage("/Index");
}
}
}
Index.cshtml 文件还包含用于为每个客户联系人创建删除按钮的标记:
<button type="submit" asp-page-handler="delete"
asp-route-id="@contact.Id">delete</button>
删除按钮采用 HTML 呈现,其 formaction
包括参数:
asp-route-id
属性指定的客户联系人 ID。asp-page-handler
属性指定的handler
。
下面是呈现的删除按钮的示例,其中客户联系人 ID 为 1
:
<button type="submit" formaction="/?id=1&handler=delete">delete</button>
选中按钮时,向服务器发送窗体 POST
请求。按照惯例,根据方案 OnPost[handler]Async
基于 handler
参数的值来选择处理程序方法的名称。
因为本示例中 handler
是 delete
,因此 OnPostDeleteAsync
处理程序方法用于处理 POST
请求。如果 asp-page-handler
设置为其他值(如 remove
),则选择名称为 OnPostRemoveAsync
的处理程序方法。以下代码显示了 OnPostDeleteAsync
处理程序:
public async Task<IActionResult> OnPostDeleteAsync(int id)
{
var contact = await _db.Customers.FindAsync(id);
if (contact != null)
{
_db.Customers.Remove(contact);
await _db.SaveChangesAsync();
}
return RedirectToPage();
}
OnPostDeleteAsync
方法:
- 接受来自查询字符串的
id
。如果 Index.cshtml 页面指令包含路由约束"{id:int?}"
,则id
来自路由数据。id
的路由数据在https://localhost:5001/Customers/2
等 URI 中指定。 - 使用
FindAsync
查询客户联系人的数据库。 - 如果找到客户联系人,则从客户联系人列表将其删除。数据库将更新。
- 调用
RedirectToPage
,重定向到根索引页 (/Index
)。
按需标记页面属性Mark page properties as required
PageModel
上的属性可通过 Required 特性进行标记:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Pages.Movies
{
public class CreateModel : PageModel
{
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
[Required(ErrorMessage = "Color is required")]
public string Color { get; set; }
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
// Process color.
return RedirectToPage("./Index");
}
}
}
有关详细信息,请参阅模型验证。
使用 OnGet 处理程序回退来处理 HEAD 请求Handle HEAD requests with an OnGet handler fallback
HEAD
请求可检索特定资源的标头。与 GET
请求不同,HEAD
请求不返回响应正文。
通常,针对 HEAD
请求创建和调用 OnHead
处理程序:
public void OnHead()
{
HttpContext.Response.Headers.Add("HandledBy", "Handled by OnHead!");
}
在 ASP.NET Core 2.1 或更高版本中,如果未定义 OnHead
处理程序,则 Razor Pages 会回退到调用 OnGet
处理程序。此行为是通过在 Startup.ConfigureServices
中调用 SetCompatibilityVersion 而启用的:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
默认模板在 ASP.NET Core 2.1 和 2.2 中生成 SetCompatibilityVersion
调用。SetCompatibilityVersion
有效地将 Razor 页面选项 AllowMappingHeadRequestsToGetHandler
设置为 true
。
可显式选择使用特定行为,而不是通过 SetCompatibilityVersion
选择使用所有行为 。通过下列代码,可选择允许将 HEAD
请求映射到 OnGet
处理程序:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.AllowMappingHeadRequestsToGetHandler = true;
});
XSRF/CSRF 和 Razor 页面XSRF/CSRF and Razor Pages
无需为防伪验证编写任何代码。Razor 页面自动将防伪标记生成过程和验证过程包含在内。
将布局、分区、模板和标记帮助程序用于 Razor 页面Using Layouts, partials, templates, and Tag Helpers with Razor Pages
页面可使用 Razor 视图引擎的所有功能。布局、分区、模板、标记帮助程序、_ViewStart.cshtml 和 _ViewImports.cshtml 的工作方式与它们在传统的 Razor 视图中的工作方式相同。
让我们使用其中的一些功能来整理此页面。
向 Pages/Shared/_Layout.cshtml 添加布局页面:
<!DOCTYPE html>
<html>
<head>
<title>Razor Pages Sample</title>
</head>
<body>
<a asp-page="/Index">Home</a>
@RenderBody()
<a asp-page="/Customers/Create">Create</a> <br />
</body>
</html>
布局:
- 控制每个页面的布局(页面选择退出布局时除外)。
- 导入 HTML 结构,例如 JavaScript 和样式表。
请参阅布局页面了解详细信息。
在 Pages/_ViewStart.cshtml 中设置 Layout 属性:
@{
Layout = "_Layout";
}
布局位于“页面/共享”文件夹中。页面按层次结构从当前页面的文件夹开始查找其他视图(布局、模板、分区)。可以从“页面/共享” 文件夹下的任意 Razor 页面使用“页面” 文件夹中的布局。
布局文件应位于 Pages/Shared 文件夹中。
建议不要 将布局文件放在“视图/共享” 文件夹中。视图/共享 是一种 MVC 视图模式。Razor 页面旨在依赖文件夹层次结构,而非路径约定。
Razor 页面中的视图搜索包含“页面” 文件夹。用于 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>
此指令将为页面设置命名空间。@model
指令无需包含命名空间。
_ViewImports.cshtml 中包含 @namespace
指令后,指定的命名空间将为在导入 @namespace
指令的页面中生成的命名空间提供前缀。生成的命名空间的剩余部分(后缀部分)是包含 _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 页面生成的命名空间与 PageModel
类相同。
@namespace
也适用于传统的 Razor 视图。
原始的 Pages/Create.cshtml 视图文件:
@page
@model RazorPagesContacts.Pages.CreateModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>
更新后的 Pages/Create.cshtml 视图文件:
@page
@model CreateModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" />
</form>
</body>
</html>
Razor 页面初学者项目包含 Pages/_ValidationScriptsPartial.cshtml ,它与客户端验证联合。
有关分部视图的详细信息,请参阅 ASP.NET Core 中的分部视图。
页面的 URL 生成URL generation for Pages
之前显示的 Create
页面使用 RedirectToPage
:
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_db.Customers.Add(Customer);
await _db.SaveChangesAsync();
return RedirectToPage("/Index");
}
应用具有以下文件/文件夹结构:
/Pages
Index.cshtml
/Customers
- Create.cshtml
- Edit.cshtml
- Index.cshtml
成功后,Pages/Customers/Create.cshtml 和 Pages/Customers/Edit.cshtml 页面将重定向到 Pages/Index.cshtml 。字符串 /Index
是用于访问上一页的 URI 的组成部分。可以使用字符串 /Index
生成 Pages/Index.cshtml 页面的 URI。例如:
Url.Page("/Index", …)
<a asp-page="/Index">My 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 中的区域。
ViewData 特性ViewData attribute
可以通过 ViewDataAttribute 将数据传递到页面。控制器或 Razor 页面模型上使用 [ViewData]
属性的属性将其值存储在 ViewDataDictionary 中并从此处进行加载。
在下面的示例中,AboutModel
包含使用 [ViewData]
标记的 Title
属性。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>
...
TempDataTempData
ASP.NET 在控制器上公开了 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。
针对一个页面的多个处理程序Multiple handlers per page
以下页面使用 asp-page-handler
标记帮助程序为两个处理程序生成标记:
@page
@model CreateFATHModel
<html>
<body>
<p>
Enter your name.
</p>
<div asp-validation-summary="All"></div>
<form method="POST">
<div>Name: <input asp-for="Customer.Name" /></div>
<input type="submit" asp-page-handler="JoinList" value="Join" />
<input type="submit" asp-page-handler="JoinListUC" value="JOIN UC" />
</form>
</body>
</html>
前面示例中的窗体包含两个提交按钮,每个提交按钮均使用 FormActionTagHelper
提交到不同的 URL。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
。
自定义路由Custom routes
使用 @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>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
前面的 ?
表示路由参数为可选。
配置和设置Configuration and settings
若要配置高级选项,请在 MVC 生成器上使用 AddRazorPagesOptions
扩展方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.RootDirectory = "/MyPages";
options.Conventions.AuthorizeFolder("/MyPages/Admin");
});
}
目前,可以使用 RazorPagesOptions
设置页面的根目录,或者为页面添加应用程序模型约定。通过这种方式,我们在将来会实现更多扩展功能。
若要预编译视图,请参阅 Razor 视图编译。
请参阅 Razor 页面入门,这篇文章以本文为基础编写。
指定 Razor 页面位于内容根目录中Specify that Razor Pages are at the content root
默认情况下,Razor 页面位于 /Pages 目录的根位置 。向 AddMvc 添加 WithRazorPagesAtContentRoot,以指定 Razor Pages 位于应用的内容根 (ContentRootPath) 中:
services.AddMvc()
.AddRazorPagesOptions(options =>
{
...
})
.WithRazorPagesAtContentRoot();
指定 Razor 页面位于自定义根目录中Specify that Razor Pages are at a custom root directory
向 AddMvc 添加 WithRazorPagesRoot,以指定 Razor 页面位于应用中自定义根目录位置(提供相对路径):
services.AddMvc()
.AddRazorPagesOptions(options =>
{
...
})
.WithRazorPagesRoot("/path/to/razor/pages");