ASP.NET Core 中文文檔 第二章 指南(4.6)Controller 方法與視圖


原文:Controller methods and views
作者:Rick Anderson
翻譯:謝煬(Kiler)
校對:孟帥洋(書緣) 、張仁建(第二年.夏) 、許登洋(Seay) 、姚阿勇(Dr.Yao) 、婁宇(Lyrics)

我們已經初步的創建了一個 movie 應用程序,但是展示並不理想。我們不希望看到 release date 字段顯示時間並且 ReleaseDate 應該是兩個單詞。
movies-Index

打開 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
    Quick Actions

  • 點擊 using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations;

Visual studio 會自動添加 using System.ComponentModel.DataAnnotations; 引用代碼。

讓我們移除多余的 using 引用代碼。它們默認以灰色字體出現。右鍵點擊 Movie.cs 文件 點擊 > Organize Usings > Remove Unnecessary 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。
Movies-Edit

EditDetails 以及 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 工具如下圖。
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 方法:
兩個 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 負責顯示錯誤信息。
Model Validation 模型驗證

movie controller 的所有 HttpGet 方法都遵循類似的模式。它們獲取一個對象(或者對象列表,比如 Index),把對象(模型)傳遞到視圖。Create 方法創建一個空的對象到 Create 視圖。諸如 Create、Edit、Delete 等之類的會修改數據的方法都會在 [HttpPost] 版本的重載方法中這樣做(譯者注:執行類似於前文所述的這些操作)。在 HTTP GET 方法中修改數據有安全風險,參考 ASP.NET MVC 提示 #46 – 不要使用刪除鏈接,因為他們制造安全漏洞 。在 HTTP GET 方法中修改數據同樣也違反 HTTP 最佳實踐以及 REST 架構模式,其中規定 GET 請求不應該更改應用程序的狀態。換句話說,執行 GET 操作應該是沒有任何副作用,不會修改您的持久化的數據。

附錄資源

返回目錄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM