原文: "Controller methods and views" 作者: "Rick Anderson" 翻譯: "謝煬(Kiler)" 校對: "孟帥洋(書緣)" 、 "張仁建(第二年.夏)" 、 "許登洋(Seay)" 、 "姚阿勇(Dr.Yao)" 、 "婁宇(Lyrics)" 我們已經初 ...
原文:Controller methods and views
作者:Rick Anderson
翻譯:謝煬(Kiler)
校對:孟帥洋(書緣) 、張仁建(第二年.夏) 、許登洋(Seay) 、姚阿勇(Dr.Yao) 、婁宇(Lyrics)
我們已經初步的創建了一個 movie 應用程式,但是展示並不理想。我們不希望看到 release date 欄位顯示時間並且 ReleaseDate 應該是兩個單詞。
打開 Models/Movie.cs 文件並添加下麵高亮的代碼行:
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")] //手動高亮
[DataType(DataType.Date)] //手動高亮
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
右鍵點擊紅色波浪線代碼行 > Quick Actions。
點擊
using System.ComponentModel.DataAnnotations;
Visual studio 會自動添加 using System.ComponentModel.DataAnnotations;
引用代碼。
讓我們移除多餘的 using
引用代碼。它們預設以灰色字體出現。右鍵點擊 Movie.cs 文件 點擊 > Organize Usings > Remove Unnecessary Usings 菜單。
更新後的代碼:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[Display(Name = "Release Date")]
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
我們會在下一篇文章中繼續發掘 DataAnnotations 的內容。Display 特性用來指定欄位的顯示名 (在本示例中 “Release Date” 會替代 “ReleaseDate”)。DataType 特性指定數據類型,在本示例是日期類型,所以欄位中存儲的時間信息不會被顯示。
瀏覽 Movies
控制器並把滑鼠懸停於 Edit 鏈接上可以看到目標 URL。
Edit、Details 以及 Delete 鏈接是由 Views/Movies/Index.cshtml 文件中的 MVC Core Anchor Tag Helper 自動生成的。
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> | //手動高亮
<a asp-action="Details" asp-route-id="@item.ID">Details</a> | //手動高亮
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a> //手動高亮
</td>
Tag Helpers 允許伺服器端代碼在 Razor 文件中創建和生成 HTML 元素。在上面的代碼中,AnchorTagHelper通過 controller 方法以及路由ID 動態生成 HTML href
屬性值。你可以在你熟悉的瀏覽器中使用 View Source 菜單或者使用 F12 工具來檢查你生成的 HTML 標簽。 F12 工具如下圖。
在 Startup.cs 文件中設置回調路由格式。
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}"); //手動高亮
});
ASP.NET Core 會把 http://localhost:1234/Movies/Edit/4
轉化成發送到 Movies
controller 的 Edit
方法的請求並帶上值為 4 的 ID
參數。(Controller 方法其實就是指代 action 方法。)
Tag Helpers 是 ASP.NET Core 中最受歡迎的新功能之一。 參考 附錄資源 獲取更多信息。
打開 Movies
controller 並查看兩個 Edit
方法:
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
[Bind]
特性是防止 over-posting (過度提交,客戶端可能發送比期望還多的數據,比如只需要2個屬性但是發送了3個屬性)的一種方法。你應該只把需要改變的屬性包含到 [Bind]
特性中。請參閱 Protect your controller from over-posting 獲取更多信息,ViewModels 提供了另一種防止 over-posting 的方法。
請註意帶第二個 Edit
方法被 [HttpPost]
特性所修飾。
[HttpPost] //手動高亮
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(movie);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(movie);
}
[HttpPost]特性指定這個 Edit
方法 只能 被 POST
請求調用。你可以把 [HttpGet]
特性應用到第一個 edit 方法,但是,不是必須的,因為 [HttpGet]
是被預設使用的。
[ValidateAntiForgeryToken] 特性是用來防止偽造請求的,會在(Views/Movies/Edit.cshtml)視圖最終呈現文件中加入反偽造標記和伺服器進行配對。edit 視圖生成反偽造標記請參考 Form Tag Helper。
<form asp-action="Edit">
Form Tag Helper 生成一個隱藏域的防偽標記必須和 Movies controller 的 Edit
方法的 [ValidateAntiForgeryToken]
產生的防偽標記相匹配。更多信息請參考 Anti-Request Forgery。
HttpGet Edit
方法獲取 movie 的 ID
參數,通過使用 Entity Framework 的 SingleOrDefaultAsync
方法查找 movie,並將選中的 movie 填充到 Edit 視圖。如果 movie 沒有找到,返回 NotFound
(HTTP 404) 響應。
// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var movie = await _context.Movie.SingleOrDefaultAsync(m => m.ID == id);
if (movie == null)
{
return NotFound();
}
return View(movie);
}
在基架系統創建 Edit 視圖的時候,會檢查 Movie 類併為它的每個屬性生成代碼以呈現 <label>
和 <input>
元素。下麵的例子展示了 Visual Studio 基架系統生成的 Edit 視圖:
@model MvcMovie.Models.Movie //手動高亮
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<form asp-action="Edit">
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="ID" />
<div class="form-group">
<label asp-for="Genre" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Genre" class="form-control" />
<span asp-validation-for="Genre" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="ReleaseDate" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ReleaseDate" class="form-control" />
<span asp-validation-for="ReleaseDate" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
你會註意到為什麼視圖模版文件的頂部會有一行 @model MvcMovie.Models.Movie
聲明呢?— 因為這個聲明指定這個視圖模版的模型期待的類型是 Movie
。
基架生成的代碼使用幾個 Tag Helper 方法來簡化 HTML 標記。 Label Tag Helper 用來顯示欄位名(“Title”、”ReleaseDate”、”Genre” 或者 “Price”)。Input Tag Helper 用來呈現 HTML <input>
元素。Validation Tag Helper 顯示關聯到屬性的錯誤信息。
運行應用程式並導航到 /Movies
URL。單擊 編輯 鏈接。在瀏覽器中查看該頁面的源代碼。為 <form>
元素生成的 HTML 如下所示。
<form action="/Movies/Edit/7" method="post"> //手動高亮
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
<div class="text-danger" />
<input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" /> //手動高亮
<div class="form-group">
<label class="control-label col-md-2" for="Genre" />
<div class="col-md-10">
<input class="form-control" type="text" id="Genre" name="Genre" value="Western" /> //手動高亮
<span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true" />
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="Price" />
<div class="col-md-10">
<input class="form-control" type="text" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" value="3.99" /> //手動高亮
<span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true" />
</div>
</div>
<!-- Markup removed for brevity -->
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" /> //手動高亮
</div>
</div>
</div>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" /> //手動高亮
</form>
HTML <form>
中的 <input>
元素的 action
屬性用於設置請求發送到 /Movies/Edit/id
URL。當點擊 Save
按鈕時表單數據會被髮送到伺服器。在 </form>
元素關閉前最後一行 </form>
展示了 XSRF 生成的隱藏域標識。
處理 POST 請求
下麵的列表顯示了 [HttpPost]
不同版本的 Edit
方法。
[HttpPost] //手動高亮
[ValidateAntiForgeryToken] //手動高亮
public async Task<IActionResult> Edit(int id, [Bind("ID,Genre,Price,ReleaseDate,Title")] Movie movie)
{
if (id != movie.ID)
{
return NotFound();
}
if (ModelState.IsValid) //手動高亮
{
try
{
_context.Update(movie); //手動高亮
await _context.SaveChangesAsync(); //手動高亮
}
catch (DbUpdateConcurrencyException)
{
if (!MovieExists(movie.ID))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index"); //手動高亮
}
return View(movie);
}
[ValidateAntiForgeryToken]
特性驗證 Form Tag Helper 生成的存放在隱藏域中的 XSRF 反偽造標記。
模型綁定機制以發送表單數據創建 Movie
對象並作為 movie
參數。ModelState.IsValid
方法驗證表單提交的數據可以用來修改(編輯或更新)一個 Movie
對象。如果數據有效,就可以保存。更新(編輯) movie 數據會被存到資料庫通過 database context 的 SaveChangesAsync
方法。數據保存完畢以後,這段代碼將用戶重定向到 MoviesController
類的 Index
方法,這個頁面顯示了改動後最新的Movie集合。
表單數據被髮布到伺服器之前,客戶端校驗會檢查所有欄位上的驗證規則。如果有任何驗證錯誤,則顯示錯誤消息,並且表單數據不會被髮送。如果禁用了 JavaScript,將不會有客戶端驗證,但伺服器端將檢測出發送數據是無效的,表單依舊會顯示出錯誤信息。在稍後的教程中,我們會探討 Model Validation 模型驗證 更多關於驗證的細節。Views/Book/Edit.cshtml 視圖模版中的 Validation Tag Helper 負責顯示錯誤信息。
movie controller 的所有 HttpGet
方法都遵循類似的模式。它們獲取一個對象(或者對象列表,比如 Index
),把對象(模型)傳遞到視圖。Create
方法創建一個空的對象到 Create
視圖。諸如 Create、Edit、Delete 等之類的會修改數據的方法都會在 [HttpPost]
版本的重載方法中這樣做(譯者註:執行類似於前文所述的這些操作)。在 HTTP GET
方法中修改數據有安全風險,參考 ASP.NET MVC 提示 #46 – 不要使用刪除鏈接,因為他們製造安全漏洞 。在 HTTP GET 方法中修改數據同樣也違反 HTTP 最佳實踐以及 REST 架構模式,其中規定 GET 請求不應該更改應用程式的狀態。換句話說,執行 GET 操作應該是沒有任何副作用,不會修改您的持久化的數據。
附錄資源
- 全球化與本地化
- Introduction to Tag Helpers
- Authoring Tag Helpers
- Anti-Request Forgery
- 防止 controller 過度提交
- ViewModels
- Form Tag Helper
- Input Tag Helper
- Label Tag Helper
- Select Tag Helper
- Validation Tag Helper