[ASP.NET MVC] Edit - 編輯的方法及View

[ASP.NET MVC] 檢視Controller產生的Edit方法及View。

Edit-編輯的方法及View

開啟 Movies Controller,可以看到兩個Edit的Action方法,如下:

// GET: Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: Movies/Edit/5
// 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
// 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

可以看到第二個Edit方法的前面有[HttpPost]屬性,這個屬性表示這個Edit方法只能由POST要求呼叫。

也可以在第一個Edit方法的前面加上[HttpGet]屬性。不過並不建議,因為[HttpGet]是預設的不用特別綁定屬性,綁定屬性也是另一種安全機制。因此,只需要在你想設定為其他屬性時,才加上。

[ValidateAntiForgeryToken]屬性建立一個隱藏的表單,Edit方法的[ValidateAntiForgeryToken]配對View(Views\Movies\Edit.cshtml)裡的@Html.AntiForgeryToken() 用來防止請求偽造

HttpGet Edit方法帶有ID參數。在Index View點選Edit時,會將ID參數帶至Controller,依照ID並透過Entity FrameworkFind方法(Edit方法中的Movie movie = db.Movies.Find(id);),回傳所選擇的電影到Edit View畫面。

如果找不到電影,會回傳HttpNotFound,如下圖指定ID參數為100時:

 

當產生Edit view的畫面時,會檢視Movies Class與每個<label><input>標籤的Class屬性,建立程式碼。

例如Edit view :

@model MyMVC.Models.Movie

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>


@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ReleaseDate, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Genre, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Genre, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Genre, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Price, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Price, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Price, "", new { @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>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

View(Views\Movies\Edit.cshtml)的頂端可以看到@model MvcMovie.Models.Movie,指定了這個View的Model為Movie(Model\Movie.cs​)

程式碼中可以看到多種的helper方法:
Html.LabelFor 顯示名稱 ("Title", "ReleaseDate", "Genre",  "Price")。

Html.EditorFor 顯示<input>標籤。

Html.ValidationMessageFor 顯示與屬相相關的驗證訊息。

執行應用程式,將網址列引導到/Movies ,按下Edit連結。在網頁中右鍵檢視原始碼,在約47行的位置,可以看到表單元素的HTML如下:

<form action="/Movies/Edit/1" method="post">
<input name="__RequestVerificationToken" type="hidden" value="27cD0FgOkCjmkxU7NcYv1wgAmvggW5R2RCixpQ0P59RBMLHNB3vUGYRxqnlw7Ds1ZHkMpY5H-yqx9dhUGnN1PkYZ6oelzseW8uwGInaIfvs1" />    
<div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        
        <input data-val="true" data-val-number="欄位 ID 必須是數字。" data-val-required="ID 欄位是必要項。" id="ID" name="ID" type="hidden" value="1" />

        <div class="form-group">
            <label class="control-label col-md-2" for="Title">Title</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" id="Title" name="Title" type="text" value="高年級實習生" />
                <span class="field-validation-valid text-danger" data-valmsg-for="Title" data-valmsg-replace="true"></span>
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="ReleaseDate">ReleaseDate</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" data-val="true" data-val-date="欄位 ReleaseDate 必須是日期。" data-val-required="ReleaseDate 欄位是必要項。" id="ReleaseDate" name="ReleaseDate" type="datetime" value="2015/10/17 上午 12:00:00" />
                <span class="field-validation-valid text-danger" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="Genre">Genre</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" id="Genre" name="Genre" type="text" value="劇情" />
                <span class="field-validation-valid text-danger" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>

        <div class="form-group">
            <label class="control-label col-md-2" for="Price">Price</label>
            <div class="col-md-10">
                <input class="form-control text-box single-line" data-val="true" data-val-number="欄位 Price 必須是數字。" data-val-required="Price 欄位是必要項。" id="Price" name="Price" type="text" value="100.00" />
                <span class="field-validation-valid text-danger" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </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>

在上面原始碼中可以看到<input>標籤於HTML<form>中。
第一行<form>的action="/Movies/Edit/1"定義了POST的URL為/Movies/Edit,點擊Save按鈕時,表單的資料將會POST到Server。 
第二行的原始碼中,@Html.AntiForgeryToken()建立了隱藏的RSRF token,防止請求偽造。

 

處理POST Request

下面顯示Edit action方法的HttpPost版本:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

ValidateAntiForgeryToken 屬性驗證在View中的@Html.AntiForgeryToken()產生XSRF token

ASP.NET MVC model binder 取得POST的FORM表單中的值,並建立一個作為movie參數傳遞movie的Movie物件。
ModelState.IsValid方法檢驗表單中提交的資料是否可用於修改(edit 或 update)Movie物件。
如果資料是有效的,電影的資料會被儲存到DB(MovieDBContext)Movie集合裡。

新的電影資料通過MovieDBContext裡的SaveChanges方法儲存到資料庫中。

在儲存資料後,程式碼return RedirectToAction("Index"),重新將使用者引向MoviesControllerIndex方法,在Index View中顯示電影列表,包括了剛剛對電影所做的變更。

View Edit.cshtml 中,Html.ValidationMessageFor 負責顯示錯誤訊息,當輸入的資料有錯誤時,可以看到欄位下方顯示錯誤訊息。

 

修改日期欄位的顯示方式

在上面的錯誤訊息中,日期欄位帶出的值 2015/10/17 上午 12:00:00 ,必須去掉後面的時間 上午 12:00:00 ,才能通過資料驗證。

接著,來修改日期欄位,讓日期欄位更好看一些,開啟 Models\Movie.cs

在最上端引用:

using System.ComponentModel.DataAnnotations;

在Movies Class中加入以下程式:

[Display(Name = "日期")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

最後的Movie.cs程式如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace MyMVC.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "日期")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString="{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

Display屬性中,Name = "日期",指定了要顯示的名稱( "日期" 會取代原本的欄位名稱 "ReleaseDate" )。
DataType屬性指定資料的型態,DataType.Date指定為日期格式,便不會在日期後面帶出時間。如同HTML標籤中的type="date"
DisplayFormat屬性則是為了防止日期格式顯示不正確的BUG。

 

ActionLink

執行應用程式,將滑鼠放在Edit連結上方,可以看到左下角的連結URL。

Edit連結是由View Views\Movies\Index.cshtml 的Html.ActionLink方法所產生:

@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |

上面的程式碼中,@Html可以幫忙產生System.Web.Mvc.WebViewPage基本的Class屬性,ActionLink方法可以建立ㄧ個可以連結到Controller的超連結。 
ActionLink方法的第一個參數表示超連結要顯示的文字,如同<a>Edit</a>。若修改為"編輯",那麼在Index畫面就可以看到超連結的文字為中文編輯
第二個參數為Action方法的名稱,"Edit"即為MoviesController的Edit Action方法
第三個參數為資料的參數new { id=item.ID },表示進入Edit Action方法時電影的id參數

在上面的Index畫面可以看到左下角的連結http://localhost:56698/Movies/Edit/1。預設的路由(檢視App_Start\RouteConfig.cs檔)URL為 "{controller}/{action}/{id}"
因此,http://localhost:56698/Movies/Edit/1 表示對MoviesController中的Edit Action方法發出參數id為1的請求。

檢視 App_Start\RouteConfig.cs 檔,MapRoute方法中url: "{controller}/{action}/{id}"提供Controller、Action 方法及參數給HTTP請求。
除此之外,MapRoute也用於HtmlHelpersActionLink設定URL的 Controller、Action 方法及參數。

routes.MapRoute
(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

也可以使用查詢字串(query string)來操作Action方法。例如:http://localhost:56698/Movies/Edit?id=1,傳遞參數id=1到MoviesController的Edit action方法,如下:

 

 END 

回目錄