一. Razor介紹
在使用ASP.NET Core Web開發時, ASP.NET Core MVC 提供了一個新特性Razor。 這樣開發Web包括了MVC框架和Razor框架。對於Razor來說它是一個新特性,在官方介紹ASP.NET Core的優點中提到“Razor Pages可以使基於頁面的編碼方式更簡單高效”。
1.1 Razor結構介紹
(1) Pages文件夾
存放所有Razor頁面,包括Razor 頁面和支持文件。 每個 Razor 頁面都是一對文件:
一個 .cshtml 文件,其中包含使用 Razor 語法的 C# 代碼的 HTML 標記。
一個 .cshtml.cs 文件,其中包含處理頁面事件的 C# 代碼。
支持文件的名稱以下划線開頭。 例如,_Layout.cshtml 文件可配置所有頁面通用的 UI 元素。 此文件設置頁面頂部的導航菜單和頁面底部的版權聲明(后面講布局時再介紹)。
(2) wwwroot 文件夾
包含靜態文件,如 HTML 文件、JavaScript 文件和 CSS 文件(后面再講)。
(3) appSettings.json
包含配置數據,如連接字符串。參考asp.net core 系列第 10和11篇
(4) Program.cs
包含程序的入口點,啟動主機。參考asp.net core 系列第 16和17篇
(5) startup.cs
包含配置應用行為的代碼,例如,是否需要同意 cookie。參考asp.net core 系列第 2篇
1.2 啟動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(); } }
1.3 @page指令
對於每一個Razor 頁面,在.cshtml視圖文件中,@page指令必須是頁面上的第一個 Razor 指令。@page 使文件轉換為一個 MVC 操作 ,這意味着它將直接處理請求, 而不通過控制器(Controllers)處理。@page
將影響其他 Razor 構造的行為。下面是一個直接處理請求的示例:
//index.cshtml.cs public class IndexModel : PageModel { public string Message { get; private set; } = "PageModel in C#"; //加載時調用 public void OnGet() { Message += $" Server time is { DateTime.Now }"; } } //Index.cshtml,直接輸出后端的Message屬性信息,也這是Razor的優勢,使用頁面的編碼方式更簡單高效。 @page @using RazorPagesIntro.Pages @model IndexModel <h2>Separate page model</h2> <p> @Model.Message </p>
1.4 頁面url路徑
URL路徑是與頁面的關聯,由頁面在文件系統中的位置決定,下表顯示了Razor Page路徑和匹配的URL:
文件路徑 | 訪問網址 |
/Pages/Index.cshtml | / or /Index |
/Pages/Contact.cshtml | /Contact |
/Pages/Store/Contact.cshtml | /Store/Contact |
/Pages/Store/Index.cshtml | /Store or /Store/Index |
二. 完整示例介紹
2.1 安裝EF數據提供程序
這里使用內存數據庫Microsoft.EntityFrameworkCore.InMemory,Entity Framework Core 和內存數據庫一起使用, 這對測試非常有用。
PM> Install-Package Microsoft.EntityFrameworkCore.InMemory
2.2 新建數據模型類(POCO )和EF上下文類
public class Customer { public int Id { get; set; } //保存不能為空,字符長度小於100 [Required, StringLength(100)] public string Name { get; set; } } public class AppDbContext : DbContext { public AppDbContext(DbContextOptions options) : base(options) { } public DbSet<Customer> Customers { get; set; } }
2.3 啟動類關鍵代碼
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(); } }
2.4 新增頁 Pages/Create
@page @model StudyRazorDemo.Pages.CreateModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <html> <body> <p> Enter your name. </p> <div asp-validation-summary="All"></div> <form method="POST"> <!-- 這里的Customer對象來自后端--> <div>Name: <input asp-for="Customer.Name" /></div> <input type="submit" /> </form> </body> </html>
public class CreateModel : PageModel { private readonly AppDbContext _db; public CreateModel(AppDbContext db) { _db = db; } //模型綁定,通過綁定使用相同的屬性顯示窗體字段<input asp-for="Customer.Name" />來減少代碼,並接受輸入,是雙向綁定。 [BindProperty] public Customer Customer { get; set; } public async Task<IActionResult> OnPostAsync() { //驗證Customer對象屬性值 if (!ModelState.IsValid) { return Page(); } //添加到EF上下文,再保存到內存數據庫 _db.Customers.Add(Customer); await _db.SaveChangesAsync(); // 重定向到index主頁 return RedirectToPage("/Index"); } }
點擊提交后,cs后端的Customer對象將自動綁定來自前端轉來的值,如下圖所示:
2.5 查詢主頁Pages/Index 關鍵代碼
<form method="post"> <table class="table"> <thead> <tr> <th>ID</th> <th>Name</th> </tr> </thead> <tbody> <!--Customers集合對象來自cs后端 --> @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>
public IList<Customer> Customers { get; private set; } //代替OnGet方法 public async Task OnGetAsync() { //異步獲取數據,EF上下文不跟蹤該集合對象 Customers = await _db.Customers.AsNoTracking().ToListAsync(); }
所有asp- 開頭的都是TagHelper內置標記,查詢如下圖所示:
2.6 修改頁Pages/Edit關鍵代碼
在主頁中(<a asp-page="./Edit" asp-route-id="@contact.Id">edit</a> )是導航到編輯頁。例如:http://localhost:5000/Edit/1。
第一行包含 @page "{id:int}"
指令,是用來路由約束,如果頁面請求未包含可轉換為 int
的路由數據,則運行時返回 HTTP 404。若要使 ID可選,請將 ?
追加到路由約束( @page "{id:int?}" )
@page "{id:int}"
@model StudyRazorDemo.Pages.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" /> <!--后端驗證Name,當失敗時提示錯誤信息 --> <span asp-validation-for="Customer.Name"></span> </div> </div> <div> <button type="submit">Save</button> </div> </form>
[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"); }
通過ID來修改數據,如下圖所示:
2.7 刪除 Pages/index
刪除動作是在index頁面完成。使用處理事件asp-page-handler="delete" 來指定后端的action 為delete方法處理。 按照約定,方法命名為: OnPost[handler]Async
,方法參數為 asp-route-id傳來的值。
/// <summary> /// index后端,根據ID刪除 /// </summary> /// <param name="id"></param> /// <returns></returns> 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(); }
參考文獻