1. 程式人生 > >用ASP.NET Core MVC 和 EF Core 構建Web應用 (二)

用ASP.NET Core MVC 和 EF Core 構建Web應用 (二)

work nal nta 多個 包括 catch web 應用 自動 選項卡

本節學習如何執行基本的 CRUD (創建、 讀取、 更新、 刪除) 操作。

自定義“詳細信息”頁

學生索引頁的基架代碼省略了 Enrollments 屬性,因為該屬性包含一個集合。 在“詳細信息”頁上,將以 HTML 表形式顯示集合的內容。

在 Controllers/StudentsController.cs 中,“詳細信息”視圖的操作方法使用 SingleOrDefaultAsync 方法檢索單個 Student 實體。 添加調用 Include 的代碼。 ThenIncludeAsNoTracking 方法,如以下突出顯示的代碼所示。

 1 public async Task<IActionResult> Details(int
? id) 2 { 3 if (id == null) 4 { 5 return NotFound(); 6 } 7 8 var student = await _context.Students 9 .Include(s => s.Enrollments) 10 .ThenInclude(e => e.Course) 11 .AsNoTracking() 12 .SingleOrDefaultAsync(m => m.ID == id);
13 14 if (student == null) 15 { 16 return NotFound(); 17 } 18 19 return View(student); 20 }

IncludeThenInclude 方法使上下文加載 Student.Enrollments 導航屬性,並在每個註冊中加載 Enrollment.Course 導航屬性。

對於返回的實體未在當前上下文生存期中更新的情況,AsNoTracking 方法將會提升性能。

將註冊添加到“詳細信息”視圖

打開 Views/Students/Details.cshtml

每個字段都使用 DisplayNameForDisplayFor 幫助器來顯示,如下面的示例中所示:

<dt>
    @Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
    @Html.DisplayFor(model => model.LastName)
</dd>

在最後一個字段之後和 </dl> 閉合標記之前,添加以下代碼以顯示註冊列表:

<dt>
    @Html.DisplayNameFor(model => model.Enrollments)
</dt>
<dd>
    <table class="table">
        <tr>
            <th>Course Title</th>
            <th>Grade</th>
        </tr>
        @foreach (var item in Model.Enrollments)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Course.Title)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Grade)
                </td>
            </tr>
        }
    </table>
</dd>

此代碼循環通過 Enrollments 導航屬性中的實體。 它將針對每個註冊顯示課程標題和成績。 課程標題從 Course 實體中檢索,該實體存儲在 Enrollments 實體的 Course 導航屬性中。

運行應用,選擇“學生”選項卡,然後單擊學生的“詳細信息”鏈接。 將看到所選學生的課程和年級列表。

更新“創建”頁

StudentsController.cs 中修改 HttpPost Create 方法,在 Bind 特性中添加 try catch 塊並刪除 ID 值。

 1 [HttpPost]
 2 [ValidateAntiForgeryToken]
 3 public async Task<IActionResult> Create(
 4     [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
 5 {
 6     try
 7     {
 8         if (ModelState.IsValid)
 9         {
10             _context.Add(student);
11             await _context.SaveChangesAsync();
12             return RedirectToAction(nameof(Index));
13         }
14     }
15     catch (DbUpdateException /* ex */)
16     {
17         //Log the error (uncomment ex variable name and write a log.
18         ModelState.AddModelError("", "Unable to save changes. " +
19             "Try again, and if the problem persists " +
20             "see your system administrator.");
21     }
22     return View(student);
23 }

此代碼將 ASP.NET MVC 模型綁定器創建的 Student 實體添加到 Students 實體集,然後將更改保存到數據庫。(模型綁定器指的是 ASP.NET MVC 功能,用戶可利用它來輕松處理使用表單提交的數據;模型綁定器將已發布的表單值轉換為 CLR 類型,並將其傳遞給操作方法的參數。 在本例中,模型綁定器將使用 Form 集合的屬性值實例化 Student 實體。)

已從 Bind 特性刪除 ID,因為 ID 是插入行時 SQL Server 將自動設置的主鍵值。 來自用戶的輸入不會設置 ID 值。

ValidateAntiForgeryToken 特性幫助抵禦跨網站請求偽造 (CSRF) 攻擊。 令牌通過 FormTagHelper 自動註入到視圖中,並在用戶提交表單時包含該令牌。 令牌由 ValidateAntiForgeryToken 特性驗證。

運行應用,選擇“學生”選項卡,並單擊“新建”。

輸入姓名和日期。 如果瀏覽器允許輸入無效日期,請嘗試輸入。 然後單擊“創建”,查看錯誤消息。

這是默認獲取的服務器端驗證; 以下突出顯示的代碼顯示 Create 方法中的模型驗證檢查。

 1 [HttpPost]
 2 [ValidateAntiForgeryToken]
 3 public async Task<IActionResult> Create(
 4     [Bind("EnrollmentDate,FirstMidName,LastName")] Student student)
 5 {
 6     try
 7     {
 8         if (ModelState.IsValid)
 9         {
10             _context.Add(student);
11             await _context.SaveChangesAsync();
12             return RedirectToAction(nameof(Index));
13         }
14     }
15     catch (DbUpdateException /* ex */)
16     {
17         //Log the error (uncomment ex variable name and write a log.
18         ModelState.AddModelError("", "Unable to save changes. " +
19             "Try again, and if the problem persists " +
20             "see your system administrator.");
21     }
22     return View(student);
23 }

將日期更改為有效值,並單擊“創建”,查看“索引”頁中顯示的新學生。

更新“編輯”頁

在 StudentController.cs 中,HttpGet Edit 方法(不具有 HttpPost 特性)使用 SingleOrDefaultAsync 方法檢索所選的 Student 實體,如 Details 方法中所示。 不需要更改此方法。

使用以下代碼替換 HttpPost Edit 操作方法。

 1 [HttpPost, ActionName("Edit")]
 2 [ValidateAntiForgeryToken]
 3 public async Task<IActionResult> EditPost(int? id)
 4 {
 5     if (id == null)
 6     {
 7         return NotFound();
 8     }
 9     var studentToUpdate = await _context.Students.SingleOrDefaultAsync(s => s.ID == id);
10     if (await TryUpdateModelAsync<Student>(
11         studentToUpdate,
12         "",
13         s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
14     {
15         try
16         {
17             await _context.SaveChangesAsync();
18             return RedirectToAction(nameof(Index));
19         }
20         catch (DbUpdateException /* ex */)
21         {
22             //Log the error (uncomment ex variable name and write a log.)
23             ModelState.AddModelError("", "Unable to save changes. " +
24                 "Try again, and if the problem persists, " +
25                 "see your system administrator.");
26         }
27     }
28     return View(studentToUpdate);
29 }

這些更改實現安全最佳做法,防止過多發布。 基架生成了 Bind 特性,並將模型綁定器創建的實體添加到具有 Modified 標記的實體集。 不建議將該代碼用於多個方案,因為 Bind 特性將清除未在 Include 參數中列出的字段中的任何以前存在的數據。

新代碼讀取現有實體並調用 TryUpdateModel以基於已發布表單數據中的用戶輸入更新已檢索實體中的字段。 Entity Framework 的自動更改跟蹤在由表單輸入更改的字段上設置 Modified 標記。 調用 SaveChanges 方法時,Entity Framework 會創建 SQL 語句,以更新數據庫行。 忽略並發沖突,並且僅在數據庫中更新由用戶更新的表列。

作為防止過多發布的最佳做法,請將希望通過“編輯”頁更新的字段列入 TryUpdateModel 參數。 (參數列表中字段列表之前的空字符串用於與表單字段名稱一起使用的前綴。)目前沒有要保護的額外字段,但是列出希望模型綁定器綁定的字段可確保以後將字段添加到數據模型時,它們將自動受到保護,直到明確將其添加到此處為止。

這些更改會導致 HttpPost Edit 方法與 HttpGet Edit 方法的方法簽名相同,因此已重命名 EditPost 方法。

運行應用,選擇“學生”選項卡,然後單擊“編輯”超鏈接。

更改某些數據並單擊“保存”。 將打開“索引”頁,將看到已更改的數據。

更新“刪除”頁

在 StudentController.cs 中,HttpGet Delete 方法的模板代碼使用 SingleOrDefaultAsync 方法來檢索所選的 Student 實體,如 Details 和 Edit 方法中所示。 但是,若要在調用 SaveChanges 失敗時實現自定義錯誤消息,請將部分功能添加到此方法及其相應的視圖中。

正如所看到的更新和創建操作,刪除操作需要兩個操作方法。 為響應 GET 請求而調用的方法將顯示一個視圖,使用戶有機會批準或取消操作。 如果用戶批準,則創建 POST 請求。 發生此情況時,將調用 HttpPost Delete方法,然後該方法實際執行刪除操作。

將 try-catch 塊添加到 HttpPost Delete 方法,以處理更新數據庫時可能出現的任何錯誤。 如果發生錯誤,HttpPost Delete 方法會調用 HttpGet Delete 方法,並向其傳遞一個指示發生錯誤的參數。 然後 HttpGet Delete 方法重新顯示確認頁以及錯誤消息,向用戶提供取消或重試的機會。

使用以下管理錯誤報告的代碼替換 HttpGet Delete 操作方法。

 1 public async Task<IActionResult> Delete(int? id, bool? saveChangesError = false)
 2 {
 3     if (id == null)
 4     {
 5         return NotFound();
 6     }
 7 
 8     var student = await _context.Students
 9         .AsNoTracking()
10         .SingleOrDefaultAsync(m => m.ID == id);
11     if (student == null)
12     {
13         return NotFound();
14     }
15 
16     if (saveChangesError.GetValueOrDefault())
17     {
18         ViewData["ErrorMessage"] =
19             "Delete failed. Try again, and if the problem persists " +
20             "see your system administrator.";
21     }
22 
23     return View(student);
24 }

此代碼接受可選參數,指示保存更改失敗後是否調用此方法。 沒有失敗的情況下調用 HttpGet Delete 方法時,此參數為 false。 由 HttpPost Delete 方法調用以響應數據庫更新錯誤時,此參數為 true,並且將錯誤消息傳遞到視圖。

HttpPost Delete 的讀取優先方法

使用以下執行實際刪除操作並捕獲任何數據庫更新錯誤的代碼替換 HttpPost Delete 操作方法(名為 DeleteConfirmed)。

 1 [HttpPost, ActionName("Delete")]
 2 [ValidateAntiForgeryToken]
 3 public async Task<IActionResult> DeleteConfirmed(int id)
 4 {
 5     var student = await _context.Students
 6         .AsNoTracking()
 7         .SingleOrDefaultAsync(m => m.ID == id);
 8     if (student == null)
 9     {
10         return RedirectToAction(nameof(Index));
11     }
12 
13     try
14     {
15         _context.Students.Remove(student);
16         await _context.SaveChangesAsync();
17         return RedirectToAction(nameof(Index));
18     }
19     catch (DbUpdateException /* ex */)
20     {
21         //Log the error (uncomment ex variable name and write a log.)
22         return RedirectToAction(nameof(Delete), new { id = id, saveChangesError = true });
23     }
24 }

此代碼檢索所選的實體,然後調用 Remove 方法以將實體的狀態設置為 Deleted調用 SaveChanges 時生成 SQL DELETE 命令。

更新“刪除”視圖

在 Views/Student/Delete.cshtml 中,在 H2 標題和 H3 標題之間添加錯誤消息,如以下示例所示:

<h2>Delete</h2>
<p class="text-danger">@ViewData["ErrorMessage"]</p>
<h3>Are you sure you want to delete this?</h3>

運行應用,選擇“學生”選項卡,並單擊“刪除”超鏈接:

單擊“刪除”。 將顯示不含已刪除學生的索引頁。

關閉數據庫連接

若要釋放數據庫連接包含的資源,完成此操作時必須盡快處理上下文實例。 ASP.NET Core 內置依賴關系註入會完成此任務。

在 Startup.cs 中,調用 AddDbContext 擴展方法來預配 ASP.NET DI 容器的 DbContext 類。 默認情況下,該方法將服務生存期設置為 ScopedScoped 表示上下文對象生存期與 Web 請求生存期一致,並在 Web 請求結束時將自動調用 Dispose 方法。

處理事務

默認情況下,Entity Framework 隱式實現事務。 在對多個行或表進行更改並調用 SaveChanges 的情況下,Entity Framework 自動確保所有更改都成功或全部失敗。 如果完成某些更改後發生錯誤,這些更改會自動回退。

非跟蹤查詢

當數據庫上下文檢索表行並創建表示它們的實體對象時,默認情況下,它會跟蹤內存中的實體是否與數據庫中的內容同步。 更新實體時,內存中的數據充當緩存並使用該數據。 在 Web 應用程序中,此緩存通常是不必要的,因為上下文實例通常生存期較短(創建新的實例並用於處理每個請求),並且通常在再次使用該實體之前處理讀取實體的上下文。

可以通過調用 AsNoTracking 方法禁用對內存中的實體對象的跟蹤。 可能想要執行的典型方案包括以下操作:

  • 在上下文生存期內,不需要更新任何實體,並且不需要 EF 自動加載具有由單獨的查詢檢索的實體的導航屬性。 在控制器的 HttpGet 操作方法中經常遇到這些情況。

  • 正在運行檢索大量數據的查詢,將只更新一小部分返回的數據。 關閉對大型查詢的跟蹤可能更有效,稍後為少數需要更新的實體運行查詢。

  • 想要附加一個實體來更新它,但之前為了其他目的,已檢索了相同的實體。 由於數據庫上下文已跟蹤了該實體,因此無法附加要更改的實體。 處理這種情況的一種方法是在早前的查詢上調用 AsNoTracking

總結

現在擁有一組完整的頁面,可對 Student 實體執行簡單的 CRUD 操作。

*****************************
*** Keep learning and growing. ***
*****************************

用ASP.NET Core MVC 和 EF Core 構建Web應用 (二)