Razor 页面是 ASP.NET Core MVC 的一个新功能,它可使基于页面的编码方式更简单高效。html
若要查找使用模型视图控制器方法的教程,请参阅 ASP.NET Core MVC 入门。git
安装 .NET Core 2.0.0 或更高版本。github
若是在使用 Visual Studio,请使用如下工做负载安装 Visual Studio 2017 版本 15.3 或更高版本:数据库
请参阅 Razor 页面入门,获取关于如何使用 Visual Studio 建立 Razor 页面项目的详细说明。visual-studio-code
Startup.cs 中已启用 Razor 页面:api
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>
上述代码看上去相似于一个 Razor 视图文件。 不一样之处在于 @page
指令。 @page
使文件转换为一个 MVC 操做 ,这意味着它将直接处理请求,而无需经过控制器处理。 @page
必须是页面上的第一个 Razor 指令。 @page
将影响其余 Razor 构造的行为。服务器
将在如下两个文件中显示使用 PageModel
类的相似页面。 Pages/Index2.cshtml 文件:mvc
@page @using RazorPages @model IndexModel2 <h2>Separate page model</h2> <p> @Model.Message </p>
Pages/Index2.cshtml.cs“代码隐藏”文件:app
using Microsoft.AspNetCore.Mvc.RazorPages; using System; namespace RazorPages { 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 |
注意:
Index
为默认页面。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
代码隐藏文件支持单元测试,可是须要你编写显式构造函数和类。 未使用 PageModel
代码隐藏文件的页面支持运行时编译,这在开发过程当中能够做为一种优点。
页面包含 OnPostAsync
处理程序方法,它在 POST
请求上运行(当用户发布窗体时)。 能够为任何 HTTP 谓词添加处理程序方法。 最多见的处理程序是:
OnGet
,用于初始化页面所需的状态。 OnGet 示例。OnPost
,用于处理窗体提交。Async
命名后缀为可选,可是按照惯例一般会将它用于异步函数。 前面示例中的 OnPostAsync
代码看上去与一般在控制器中编写的内容类似。 前面的代码一般用于 Razor 页面。 多数 MVC 基元(例如模型绑定、验证和操做结果)都是共享的。
以前的 OnPostAsync
方法:
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _db.Customers.Add(Customer); await _db.SaveChangesAsync(); return RedirectToPage("/Index"); }
OnPostAsync
的基本流:
检查验证错误。
成功输入数据后,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" />
) 来减小代码,并接受输入。
主页 (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>
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>
定位点标记帮助程序 使用 asp-route-{value} 属性生成“编辑”页面的连接。 此连接包含路由数据及联系人 ID。 例如 http://localhost:5000/Edit/1
。
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(未找到)错误。
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
的页面处理程序方法。
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
。FindAsync
查询客户联系人的数据库。RedirectToPage
,重定向到根索引页 (/Index
)。无需为防伪验证编写任何代码。 Razor 页面自动将防伪标记生成过程和验证过程包含在内。
页面可以使用 Razor 视图引擎的全部功能。 布局、分区、模板、标记帮助程序、_ViewStart.cshtml 和 _ViewImports.cshtml 的工做方式与它们在传统的 Razor 视图中的工做方式相同。
咱们来使用其中的一些功能来整理此页面。
向 Pages/_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>
布局:
请参阅布局页面了解详细信息。
在 Pages/_ViewStart.cshtml 中设置 Layout 属性:
@{ Layout = "_Layout"; }
注意:布局位于“页面”文件夹中。 页面按层次结构从当前页面的文件夹开始查找其余视图(布局、模板、分区)。 能够从“页面”文件夹下的任意 Razor 页面使用“页面”文件夹中的布局。
建议不要将布局文件放在“视图/共享”文件夹中。 视图/共享 是一种 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 的文件夹与包含页面的文件夹之间以点分隔的相对路径。
例如,代码隐藏文件 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 页面生成的命名空间与代码隐藏文件相同. 已对 @namespace
指令进行设计,所以添加到项目的 C# 类和页面生成的代码可直接工做,而无需添加代码隐藏文件的 @using
指令。
注意:@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,它与客户端验证联合。
以前显示的 Create
页面使用 RedirectToPage
:
public async Task<IActionResult> OnPostAsync() { if (!ModelState.IsValid) { return Page(); } _db.Customers.Add(Customer); await _db.SaveChangesAsync(); return RedirectToPage("/Index"); }
应用具备如下文件/文件夹结构:
/Pages
/Customer
成功后,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
参数与当前页的路径来计算目标页面的名称。
构建结构复杂的站点时,相对名称连接颇有用。 若是使用相对名称连接文件夹中的页面,则能够重命名该文件夹。 全部连接仍然有效(由于这些连接未包含此文件夹名称)。
ASP.NET 在控制器上公开了 TempData 属性。 此属性可存储数据,直至数据被读取。 Keep
和 Peek
方法可用于检查数据,而不执行删除。 多个请求须要数据时,TempData
有助于进行重定向。
[TempData]
是 ASP.NET Core 2.0 中的新属性,在控制器和页面上受支持。
下面的代码使用 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>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?.ToUpper(); 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 路径为 http://localhost:5000/Customers/CreateFATH?handler=JoinList
。 提交到 OnPostJoinListUCAsync
的 URL 路径为 http://localhost:5000/Customers/CreateFATH?handler=JoinListUC
。
若是你不喜欢 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>
前面的路由将处理程序放在了 URL 路径中,而不是查询字符串中。 handler
前面的 ?
表示路由参数为可选。
可使用 @page
将其余段和参数添加到页面的路由中。 其中的任何内容均会被追加到页面的默认路由中。 不支持使用绝对路径或虚拟路径更改页面的路由(例如 "~/Some/Other/Path"
)。
若要配置高级选项,请在 MVC 生成器上使用 AddRazorPagesOptions
扩展方法:
public void ConfigureServices(IServiceCollection services) { services.AddMvc() .AddRazorPagesOptions(options => { options.RootDirectory = "/MyPages"; options.Conventions.AuthorizeFolder("/MyPages/Admin"); }); }
目前,可使用 RazorPagesOptions
设置页面的根目录,或者为页面添加应用程序模型约定。 咱们但愿未来能够经过这种方式实现更多扩展功能。