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
設置頁面的根目錄,或者爲頁面添加應用程序模型約定。 咱們但願未來能夠經過這種方式實現更多擴展功能。