共用方式為


檢查電影控制器的動作方法和檢視

Rick Anderson

注意

這裡提供本教學課程的更新版本,其中使用 ASP.NET MVC 5 和 Visual Studio 2013。 新的教學指示更安全、更易於遵循且示範更多功能。

在本節中,我們將檢查針對電影控制器產生的動作方法和檢視。 接著我們將新增自訂搜尋頁面。

執行應用程式並瀏覽至 Movies 控制器,方法是將 /Movies 附加到瀏覽器網址列中的 URL。 將滑鼠指標停留在 [編輯] 連結上,查看將連至的 URL。

EditLink_sm

[編輯] 連結是由 Views\Movies\Index.cshtml 檢視中的 Html.ActionLink 方法所產生:

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

Html.ActionLink

Html 物件這個協助程式會使用 System.Web.Mvc.WebViewPage 基礎類別來公開。 協助程式的 ActionLink 方法可讓您輕鬆地以動態方式產生 HTML 超連結,連結至控制器的動作方法。 ActionLink 方法的第一個引數是需要轉譯的連結文字 (例如 <a>Edit Me</a>)。 第二個引數是要叫用的動作方法名稱。 最後一個引數是匿名物件,會產生路由資料 (在此例中,ID 為 4)。

上一個影像中顯示的已產生連結為 http://localhost:xxxxx/Movies/Edit/4。 預設路由 (在 App_Start\RouteConfig.cs 中建立) 會採用 URL 模式 {controller}/{action}/{id}。 因此,ASP.NET 會將 http://localhost:xxxxx/Movies/Edit/4 轉譯成對 Movies 控制器提出 Edit 動作方法要求,其參數 ID 等於 4 檢驗 App_Start\RouteConfig.cs 檔案的下列程式碼。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

您也可以使用查詢字串傳遞動作方法參數。 例如,URL http://localhost:xxxxx/Movies/Edit?ID=4 也會將參數 ID 4 傳遞給 Movies 控制器的 Edit 動作方法。

EditQueryString

開啟 Movies 控制器。 以下顯示兩種 Edit 動作方法。

//
// GET: /Movies/Edit/5

public ActionResult Edit(int id = 0)
{
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

//
// POST: /Movies/Edit/5

[HttpPost]
public ActionResult Edit(Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

請注意,第二個 Edit 動作方法的前面是 HttpPost 屬性。 此屬性指定 Edit 方法的多載只能由 POST 要求叫用。 您可以將 HttpGet 屬性套用至第一個編輯方法,但因為這是預設值,因此不一定要執行此動作。 (我們會參照將 HttpGet 屬性隱含指派為 HttpGet 方法的動作方法)。

HttpGet Edit 方法會採用電影 ID 參數,運用 Entity Framework Find 方法查詢電影,並將選取的電影傳回「編輯」檢視。 如果呼叫方法時沒有參數,EditID 參數會指定預設值為零。 找不到電影時會傳回 HttpNotFound。 當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 以下範例示範產生的編輯檢視:

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>Movie</legend>

        @Html.HiddenFor(model => model.ID)

        <div class="editor-label">
            @Html.LabelFor(model => model.Title)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Title)
            @Html.ValidationMessageFor(model => model.Title)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.ReleaseDate)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.ReleaseDate)
            @Html.ValidationMessageFor(model => model.ReleaseDate)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Genre)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Genre)
            @Html.ValidationMessageFor(model => model.Genre)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Price)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Price)
            @Html.ValidationMessageFor(model => model.Price)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

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

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

請注意檢視範本的檔案頂端有 @model MvcMovie.Models.Movie 陳述式,用來指定預期的檢視範本模型為 Movie 類型。

已建立結構的程式碼會使用數個協助程式方法來簡化 HTML 標記。 Html.LabelFor 協助程式會顯示欄位的名稱 (「Title」、「ReleaseDate」、「Genre」 或 「Price」)。 Html.EditorFor 協助程式會轉譯 HTML <input> 元素。 Html.ValidationMessageFor 協助程式則會顯示與該屬性相關聯的任何驗證訊息。

執行應用程式,並前往 /Movies URL。 按一下 Edit 連結。 在瀏覽器中,檢視頁面的原始檔。 表單元素的 HTML 如下所示。

<form action="/Movies/Edit/4" method="post">    <fieldset>
        <legend>Movie</legend>

        <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

        <div class="editor-label">
            <label for="Title">Title</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
            <span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="ReleaseDate">ReleaseDate</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
            <span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="Genre">Genre</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
            <span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
        </div>

        <div class="editor-label">
            <label for="Price">Price</label>
        </div>
        <div class="editor-field">
            <input class="text-box single-line" 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" type="text" value="2.99" />
            <span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
</form>

<input> 元素位於 UTML <form> 元素中,而後者的 action 屬性設為發佈到 /Movies/Edit URL。 按一下 [編輯] 按鈕,表單資料就會張貼至伺服器。

處理 POST 要求

下列清單顯示 HttpPost 版本的 Edit 動作方法。

[HttpPost] 
public ActionResult Edit(Movie movie)  
{ 
    if (ModelState.IsValid)  
    { 
        db.Entry(movie).State = EntityState.Modified; 
        db.SaveChanges(); 
        return RedirectToAction("Index"); 
    } 
    return View(movie); 
}

ASP.NET MVC 模型繫結器會採用已發佈的表單值,並建立 Movie 物件來傳遞 movie 參數。 ModelState.IsValid 方法會驗證表單中提交的資料可用於修改 (編輯或更新) Movie 物件。 如果數據有效,電影數據會儲存至 Movies 實例的 db(MovieDBContext 集合。 呼叫 MovieDBContextSaveChanges 方法,新的電影資料就會儲存到資料庫。 儲存數據之後,程式代碼會將使用者重新導向至 Index 類別的 MoviesController action 方法,該方法會顯示電影集合的 ,包括剛才所做的變更。

如果張貼的值無效,表單會重新顯示值。 「Edit.cshtml」檢視範本的 Html.ValidationMessageFor 協助程式負責顯示相應的錯誤訊息。

abcNotValid

注意

若要針對小數點使用逗號 (“,”) 的非英文地區設定支援 jQuery 驗證,您必須包含globalize.js和特定文化特性/globalize.cultures.js檔案(from https://github.com/jquery/globalize ) 和 JavaScript 才能使用 Globalize.parseFloat 。 下列程式代碼顯示 Views\Movies\Edit.cshtml 檔案的修改,以使用 “fr-FR” 文化特性:

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script src="~/Scripts/globalize.js"></script>
    <script src="~/Scripts/globalize.culture.fr-FR.js"></script>
    <script>
        $.validator.methods.number = function (value, element) {
            return this.optional(element) ||
                !isNaN(Globalize.parseFloat(value));
        }
        $(document).ready(function () {
            Globalize.culture('fr-FR');
        });
    </script>
    <script>
        jQuery.extend(jQuery.validator.methods, {    
            range: function (value, element, param) {        
                //Use the Globalization plugin to parse the value        
                var val = $.global.parseFloat(value);
                return this.optional(element) || (
                    val >= param[0] && val <= param[1]);
            }
        });

    </script>
}

小數位段可能需要逗號,而不是小數點。 做為暫時修正,您可以將全球化元素新增至專案根 web.config 檔案。 下列程式代碼所示為將文化設為美國英文的全球化元素。

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

所有 HttpGet 方法都遵循相似的模式。 它們會取得電影物件 (在 Index 的案例為物件清單),並將模型傳遞至檢視。 Create 方法會將空白電影物件傳遞至「建立」檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 HttpPost 多載中執行這個動作。 修改 HTTP GET 方法中的數據是安全性風險。 修改 GET 方法的資料也違反 HTTP 最佳做法和架構性 REST 模式,此模式指定 GET 要求不應變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

新增搜尋方法和搜尋檢視

在本節中,您將新增 SearchIndex 動作方法,其可依「類型」或「名稱」搜尋電影。 使用 /Movies/SearchIndex URL 即可取得。 要求會顯示 HTML 表單,其中包含使用者可以輸入的輸入元素,以便搜尋電影。 使用者提交表單時,動作方法會取得使用者張貼的搜尋值,並將值用於搜尋資料庫。

顯示 SearchIndex 表單

首先,將 SearchIndex 動作方法新增至現有的 MoviesController 類別。 方法會傳回包含 HTML 表單的檢視。 程式碼如下:

public ActionResult SearchIndex(string searchString) 
{           
    var movies = from m in db.Movies 
                 select m; 
 
    if (!String.IsNullOrEmpty(searchString)) 
    { 
        movies = movies.Where(s => s.Title.Contains(searchString)); 
    } 
 
    return View(movies); 
}

SearchIndex 方法的第一行會建立以下 LINQ 查詢來選取電影:

var movies = from m in db.Movies 
             select m;

目前我們已定義查詢,但尚未對資料存放區執行查詢。

如果 searchString 參數包含字串,則系統會使用下列程式碼修改電影查詢,藉此篩選搜尋字串的值:

if (!String.IsNullOrEmpty(searchString)) 
{ 
    movies = movies.Where(s => s.Title.Contains(searchString)); 
}

上述 s => s.Title 程式碼是 Lambda 運算式。 在以方法為基礎的 LINQ 查詢中,Lambda 會在標準查詢運算子方法 (例如上述程式碼的 Where 方法) 中當作引數。 在您定義 LINQ 查詢,或藉由呼叫 WhereOrderBy 這類方法修改 LINQ 查詢時,它並不會執行。 反之,查詢的執行作業將會延遲,即評估運算式會延遲,直到系統逐一查看實現值或呼叫 ToList 方法。 在 SearchIndex 範例中,查詢將於 SearchIndex 檢視中執行。 如需延後查詢執行的詳細資訊,請參閱查詢執行

接下來,您可以實作 SearchIndex 檢視,向使用者顯示表單。 在 SearchIndex 方法內按滑鼠右鍵,然後按一下 [新增檢視]。 在 [新增檢視] 對話方塊中,指定您要將 Movie 物件傳遞至檢視範本做為模型類別。 在「Scaffold 範本」清單中選擇 [清單],然後按一下 [新增]

AddSearchView

按一下 [新增] 按鈕,即可建立 Views\Movies\SearchIndex.cshtml 檢視範本。 由於您在 Scaffold 範本清單中選取 [列表 ],因此 Visual Studio 會自動在檢視中 產生一些預設 標記。 Scaffolding 建立了 HTML 表單。 其中檢查了 Movie 類別,並建立程式碼呈現類別中每個屬性的 <label> 元素。 下列清單顯示產生的建立檢視:

@model IEnumerable<MvcMovie.Models.Movie> 
 
@{ 
    ViewBag.Title = "SearchIndex"; 
} 
 
<h2>SearchIndex</h2> 
 
<p> 
    @Html.ActionLink("Create New", "Create") 
</p> 
<table> 
    <tr> 
        <th> 
            Title 
        </th> 
        <th> 
            ReleaseDate 
        </th> 
        <th> 
            Genre 
        </th> 
        <th> 
            Price 
        </th> 
        <th></th> 
    </tr> 
 
@foreach (var item in Model) { 
    <tr> 
        <td> 
            @Html.DisplayFor(modelItem => item.Title) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.ReleaseDate) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.Genre) 
        </td> 
        <td> 
            @Html.DisplayFor(modelItem => item.Price) 
        </td> 
        <td> 
            @Html.ActionLink("Edit", "Edit", new { id=item.ID }) | 
            @Html.ActionLink("Details", "Details", new { id=item.ID }) | 
            @Html.ActionLink("Delete", "Delete", new { id=item.ID }) 
        </td> 
    </tr> 
} 
 
</table>

執行應用程式,並瀏覽至 /Movies/SearchIndex。 將查詢字串 (例如 ?searchString=ghost) 附加至 URL。 隨即顯示篩選過的電影。

SearchQryStr

如果您將 SearchIndex 方法的簽章變更為包含名為 id 的參數,則 id 參數會針對 Global.asax 檔案中設定的預設路由,比對 {id} 預留位置。

{controller}/{action}/{id}

原始的 SearchIndex 方法看起來像這樣:

public ActionResult SearchIndex(string searchString) 
{           
    var movies = from m in db.Movies 
                 select m; 
 
    if (!String.IsNullOrEmpty(searchString)) 
    { 
        movies = movies.Where(s => s.Title.Contains(searchString)); 
    } 
 
    return View(movies); 
}

修改 SearchIndex 過的方法則如下所示:

public ActionResult SearchIndex(string id) 
{ 
    string searchString = id; 
    var movies = from m in db.Movies 
                 select m; 
 
    if (!String.IsNullOrEmpty(searchString)) 
    { 
        movies = movies.Where(s => s.Title.Contains(searchString)); 
    } 
 
    return View(movies); 
}

您現在可以將搜尋標題作為路由資料 (URL 區段) 傳遞,而不是作為查詢字串值。

SearchRouteData

但是,您不能期望使用者在每次想要搜尋電影時修改 URL。 現在您可新增 UI 來協助篩選電影。 如果您已變更 SearchIndex 方法的簽章,藉此測試如何傳遞路由繫結的 ID 參數,請改回原狀,使 SearchIndex 方法採用名為 searchString 的字串參數:

public ActionResult SearchIndex(string searchString) 
{           
     var movies = from m in db.Movies 
                  select m; 
 
    if (!String.IsNullOrEmpty(searchString)) 
    { 
        movies = movies.Where(s => s.Title.Contains(searchString)); 
    } 
 
    return View(movies); 
}

開啟「Views\Movies\SearchIndex.cshtml」檔案,然後緊接在 @Html.ActionLink("Create New", "Create") 之後新增下列內容:

@using (Html.BeginForm()){    
         <p> Title: @Html.TextBox("SearchString")<br />  
         <input type="submit" value="Filter" /></p> 
        }

下列範例顯示「Views\Movies\SearchIndex.cshtml」檔案的一部分,包含新增的篩選標記。

@model IEnumerable<MvcMovie.Models.Movie> 
 
@{ 
    ViewBag.Title = "SearchIndex"; 
} 
 
<h2>SearchIndex</h2> 
 
<p> 
    @Html.ActionLink("Create New", "Create") 
     
     @using (Html.BeginForm()){    
         <p> Title: @Html.TextBox("SearchString") <br />   
         <input type="submit" value="Filter" /></p> 
        } 
</p>

Html.BeginForm 協助程式會建立開頭的 <form> 標記。 使用者按一下 [篩選] 按鈕提交表單後,Html.BeginForm 協助程式會將表單張貼至表單本身。

執行應用程式並嘗試搜尋電影。

執行應用程式的螢幕快照,並嘗試搜尋電影。

SearchIndex 方法沒有 HttpPost 多載。 您不需要多載,因為此方法不會變更應用程式的狀態,只會篩選資料。

您可以新增下列 HttpPost SearchIndex 方法。 這種情況下,動作啟動程式會比對 HttpPost SearchIndex 方法,而 HttpPost SearchIndex 方法會如下列影像所示執行。

[HttpPost] 
public string SearchIndex(FormCollection fc, string searchString) 
{ 
    return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>"; 
}

SearchPostGhost

不過,即使您新增這個 HttpPost 版本的 SearchIndex 方法,在如何全部實作此方法方面仍然有其限制。 假設您想要將特定的搜尋加為書籤,或者想要傳送連結給朋友,讓他們可以點選來查看相同的電影篩選清單。 請注意,HTTP POST 要求的 URL 與 GET 要求的 URL (localhost:xxxxx/Movies/SearchIndex) 相同,URL 中沒有搜尋資訊。 此時,搜尋字串資訊會以表單欄位值的形式傳送至伺服器。 這表示您無法擷取該搜尋資訊,以便加入書籤或以 URL 傳送給好友。

解決方案是使用的多載 BeginForm ,指定 POST 要求應該將搜尋資訊新增至 URL,而且應該路由至 方法的 SearchIndex HttpGet 版本。 刪除現有的無參數 BeginForm 方法,並替換為以下內容:

@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))

BeginFormPost_SM

現在,如果您提交搜尋,URL 會包含搜尋查詢字串。 即使您有 HttpPost SearchIndex 方法,搜尋也會移至 HttpGet SearchIndex 動作方法。

SearchIndexWithGetURL

新增依類型搜尋

如果您已新增 SearchIndex 方法的 HttpPost 版本,請先將它刪除。

接下來,您會新增一項功能,讓使用者依據類型搜尋電影。 以下列程式碼取代 SearchIndex 方法:

public ActionResult SearchIndex(string movieGenre, string searchString) 
{ 
    var GenreLst = new List<string>(); 
 
    var GenreQry = from d in db.Movies 
                   orderby d.Genre 
                   select d.Genre; 
    GenreLst.AddRange(GenreQry.Distinct()); 
    ViewBag.movieGenre = new SelectList(GenreLst); 
 
    var movies = from m in db.Movies 
                 select m; 
 
    if (!String.IsNullOrEmpty(searchString)) 
    { 
        movies = movies.Where(s => s.Title.Contains(searchString)); 
    } 
 
    if (string.IsNullOrEmpty(movieGenre)) 
        return View(movies); 
    else 
    { 
        return View(movies.Where(x => x.Genre == movieGenre)); 
    } 
 
}

這個版本的 SearchIndex 方法會採用額外參數,也就是 movieGenre。 前幾行程式碼會建立 List 物件,藉此保存資料庫中的電影類型。

下列程式碼是一種 LINQ 查詢,其會從資料庫中擷取所有的內容類型。

var GenreQry = from d in db.Movies 
                   orderby d.Genre 
                   select d.Genre;

程式碼會使用一般的 List 集合的 AddRange 方法,將所有不同類型新增至清單。 (如果沒有 Distinct 修飾詞,系統會新增重複的類型,例如在我們的範例中,喜劇就會新增兩次)。 程式碼接著會將類型清單儲存在 ViewBag 物件中。

下列程式碼示範如何檢查 movieGenre 參數。 如果參數不是空白,程式碼會進一步縮限電影查詢,將選取的電影限制為指定類型。

if (string.IsNullOrEmpty(movieGenre)) 
        return View(movies); 
else 
{ 
    return View(movies.Where(x => x.Genre == movieGenre)); 
}

將標記新增到 SearchIndex 檢視,支援依類型搜尋

Html.DropDownList 協助程式新增至「Views\Movies\SearchIndex.cshtml」檔案,就在 TextBox 協助程式之前。 完成的標記如下所示:

<p> 
    @Html.ActionLink("Create New", "Create") 
    @using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){     
         <p>Genre: @Html.DropDownList("movieGenre", "All")   
           Title: @Html.TextBox("SearchString")   
         <input type="submit" value="Filter" /></p> 
        } 
</p>

執行應用程式,並瀏覽至 /Movies/SearchIndex。 嘗試依類型、電影名稱搜尋,以及同時用這兩個條件搜尋。

執行應用程式的螢幕快照,並嘗試依內容類型電影名稱和兩個準則進行搜尋。

在本節中,您檢查了由架構產生的 CRUD 動作方法和檢視。 您建立了搜尋動作方法和檢視,讓使用者依電影標題和類型進行搜尋。 下一節,您將瞭解如何將屬性新增至 Movie 模型,以及如何新增初始設定式,自動建立測試資料庫。