次の方法で共有


Edit メソッドと Edit ビューの確認 (VB)

作成者: Rick Anderson

このチュートリアルでは、Microsoft Visual Web Developer 2010 Express Service Pack 1 (Microsoft Visual Studio の無料バージョン) を使用した ASP.NET MVC Web アプリケーション構築の基本事項を説明します。 開始する前に、以下に示す前提条件がインストールされていることを確認してください。 次のリンクをクリックすると、これらをすべてインストールできます: Web Platform Installer。 また、次のリンクを使用して前提条件となるソフトウェアを個別にインストールすることもできます。

Visual Web Developer 2010 ではなく Visual Studio 2010 を使用する場合は、Visual Studio 2010 の前提条件のリンクをクリックして、前提条件をインストールします。

このトピックを学習する際に、VB.NET ソース コードを含む Visual Web Developer プロジェクトを使用できます。 VB.NET バージョンをダウンロードします。 C# を使用する場合は、このチュートリアルの C# バージョンに切り替えてください。

このセクションでは、ムービー コントローラーに関する生成対象のアクション メソッドとビューを調べます。 その後、カスタム検索ページを追加します。

アプリケーションを実行し、ブラウザーのアドレス バーに入力されている URL に "/Movies" を追加して Movies コントローラーを表示します。 Edit リンクの上にマウス ポインターを置くと、リンク先の URL が表示されます。

スクリーンショットには、いずれかの映画の [編集] リンクが選択されている MVC Move App が示されています。

この [Edit (編集)] リンクは、Views\Movies\Index.vbhtml ビューの Html.ActionLink メソッドによって生成されたものです。

@Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |

コード エディターの Html.ActionLink を示すスクリーンショット。

Html オブジェクトは、WebViewPage 基底クラスのプロパティで公開されているヘルパーです。 このヘルパーの ActionLink メソッドを使用すると、コントローラー上のアクション メソッドにリンクする HTML ハイパーリンクの動的な生成を簡単に行えます。 ActionLink メソッドの第 1 引数は、リンクとして表示するテキストです (例: <a>Edit Me</a>)。 第 2 引数は、呼び出すアクション メソッドの名前です。 最後の引数は、ルート データを生成する匿名オブジェクトです (この場合は ID 4)。

前の図では http://localhost:xxxxx/Movies/Edit/4 というリンクが生成されています。 既定ルートの場合、{controller}/{action}/{id} という URL パターンが使用されます。 したがって、ASP.NET は、http://localhost:xxxxx/Movies/Edit/4 を、Movies コントローラーの Edit アクション メソッドへの要求に変換し、パラメーター ID は 4 になります。

クエリ文字列を使用してアクション メソッドのパラメーターを渡すこともできます。 たとえば、URL http://localhost:xxxxx/Movies/Edit?ID=4 でも Movies コントローラーの Edit アクション メソッドに ID パラメーターとして 4 が渡されます。

EditQueryString

Movies コントローラーを開きます。 2 つの Edit アクション メソッドを以下に示します。

'
' GET: /Movies/Edit/5

Function Edit(id As Integer) As ViewResult
    Dim movie As Movie = db.Movies.Find(id)
    Return View(movie)
End Function

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

2 番目の Edit アクション メソッドの前に HttpPost 属性が付いていることに注意してください。 この属性は、Edit メソッドのオーバーロードは POST 要求の場合にのみ呼び出し可能であることを指定するものです。 第 1 の Edit メソッドには HttpGet 属性を適用できますが、そちらは既定で適用済みの扱いになるため必須ではありません (HttpGet 属性が暗黙的に割り当てられているアクション メソッドを HttpGet メソッドと呼びます)。

HttpGet Edit メソッドは movie ID パラメーターを受け取り、Entity Framework Find メソッドを使用してムービーを検索し、選択したムービーを編集ビューに返します。 スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 生成された [Edit (編集)] ビューの例を次に示します。

@ModelType MvcMovie.Movie

@Code
    ViewData("Title") = "Edit"
End Code

<h2>Edit</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@Using Html.BeginForm()
    @Html.ValidationSummary(True)
    @<fieldset>
        <legend>Movie</legend>

        @Html.HiddenFor(Function(model) model.ID)

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

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

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

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

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

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

ビュー テンプレートのファイルの冒頭に置かれた @ModelType MvcMovie.Models.Movie ステートメントは、このビューにおいて、ビュー テンプレートのモデルとして Movie 型が想定されていることを示します。

このスキャフォールディングされたコードでは、HTML マークアップを合理的に行うためにいくつかのヘルパー メソッドを使用しています。 Html.LabelFor ヘルパーは、フィールドの名前 ("Title"、"ReleaseDate"、"Genre"、または "Price") を表示します。 Html.EditorFor ヘルパーは HTML <input> 要素を表示します。 Html.ValidationMessageFor ヘルパーは、指定のプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL に移動します。 [編集] リンクをクリックします。 ブラウザーで、ページのソースを表示します。 このページの HTML は次の例のようになります (見やすいようにメニューのマークアップを省略しています)。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Edit</title>
    <link href="/Content/Site.css" rel="stylesheet" type="text/css" />
    <script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
    <script src="/Scripts/modernizr-1.7.min.js" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>MVC Movie App</h1>
            </div>
           ...
        </header>
        <section id="main">

<h2>Edit</h2>

<script src="/Scripts/jquery.validate.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>

<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-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="9.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>
<div>
    <a href="/Movies">Back to List</a>
</div>

        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

<input> 要素は HTML <form> 要素内にあり、このフォームの action 属性は /Movies/Edit URL への post に設定されています。 フォーム データは、[Edit (編集)] ボタンがクリックされるとサーバーに POST 送信されます。

POST 要求の処理

次のリストでは、Edit アクション メソッドの HttpPost バージョンを示します。

'
' POST: /Movies/Edit/5

<HttpPost()>
Function Edit(movie As Movie) As ActionResult
    If ModelState.IsValid Then
        db.Entry(movie).State = EntityState.Modified
        db.SaveChanges()
        Return RedirectToAction("Index")
    End If

    Return View(movie)
End Function

ASP.NET フレームワーク モデル バインダーは、POST されたフォーム値を受け取り、movie パラメーターとして渡された Movie オブジェクトを作成します。 コード内の ModelState.IsValid チェックでは、フォームで送信されたデータを使用して Movie オブジェクトに変更を加えられるかどうかを確認します。 データが有効であれば、映画データを MovieDBContext インスタンスの Movies コレクションに保存します。 次に、新しい映画データをデータベースに保存するために MovieDBContextSaveChanges メソッドを呼び出します。変更内容はデータベースに保持されます。 データを保存した後は、ユーザーを MoviesController クラスの Index アクション メソッドにリダイレクトします。これにより、更新された映画が映画の一覧に表示されます。

送信された値が有効でない場合は、フォームに再表示されます。 Edit.vbhtml ビュー テンプレートの Html.ValidationMessageFor ヘルパーは、適切なエラー メッセージの表示を処理します。

abcNotValid

ロケールに関する注意: 通常の作業を英語以外のロケールで行う場合は、「英語以外のロケールでの ASP.NET MVC 3 検証のサポート」を参照してください。

Edit メソッドの堅牢性を高める

スキャフォールディング システムによって生成された HttpGet Edit メソッドは、渡された ID が有効であることを確認しません。 ユーザーによって URL (http://localhost:xxxxx/Movies/Edit) から ID セグメントが削除された場合、次のエラーが表示されます。

Null_ID

また、データベースにない ID (例: http://localhost:xxxxx/Movies/Edit/1234) がユーザーから渡される可能性もあります。 この制限に対処するために、 HttpGet Edit アクション メソッドに 2 つの変更を加えることができます。 まず、ID が明示的に渡されない場合の既定値が 0 になるように ID パラメーターを変更します。 さらに、映画オブジェクトをビュー テンプレートに返す前に、Find メソッドで実際に映画が見つかるかどうかを確認します。 更新後の Edit メソッドは以下のようになります。

Public Function Edit(Optional ByVal id As Integer = 0) As ActionResult
    Dim movie As Movie = db.Movies.Find(id)
    If movie Is Nothing Then
        Return HttpNotFound()
    End If
    Return View(movie)
End Function

映画が見つからないときは、HttpNotFound メソッドが呼び出されます。

HttpGet のメソッドは、すべて同様のパターンに従います。 映画 オブジェクト (Index の場合はオブジェクトのリスト) を取得し、モデルをビューに渡します。 Create メソッドは、空の映画オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、メソッドの HttpPost のオーバーロードでそれを行います。 HTTP GET メソッドでデータを変更することは、セキュリティ上のリスクです。 また、GET メソッドでのデータ変更は HTTP のベスト プラクティスに反するほか、アーキテクチャ REST パターンの、GET 要求でアプリケーションの状態を変更すべきでないという規定にも違反します。 つまり、GET 操作は、副作用のない安全な操作として処理されるべきです。

検索メソッドと検索ビューを追加する

このセクションでは、映画をジャンルや名前で検索できる SearchIndex アクション メソッドを追加します。 これは /Movies/SearchIndex URL で利用できるようになります。 ユーザーからこの要求を受けたときには、映画を検索するための入力要素を含む HTML フォームを表示します。 そのフォームでユーザーの送信操作が行われると、アクション メソッドが、送信内容から検索用の値を取得し、データベースを検索します。

SearchIndx_SM

SearchIndex フォームを表示する

まず、既存の MoviesController クラスに SearchIndex アクション メソッドを追加します。 このメソッドは、HTML フォームを含んだビューを返します。 のコードを次に示します。

Public Function SearchIndex(ByVal searchString As String) As ActionResult
    Dim movies = From m In db.Movies
                 Select m 

    If Not String.IsNullOrEmpty(searchString) Then 
        movies = movies.Where(Function(s) s.Title.Contains(searchString)) 
    End If
    Return View(movies) 
End Function

SearchIndex メソッド内の 1 行目では、次のように、映画を選択するための LINQ クエリを作成しています。

Dim movies = From m In db.Movies    Select m

この時点でクエリが定義されますが、まだデータ ストアに対して実行はされません。

searchString パラメーターに文字列が含まれる場合、以下のコードを使用して、検索文字列の値でフィルターするようにムービー クエリが変更されます。

If Not String.IsNullOrEmpty(searchString) Then
movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If

LINQ クエリは、定義されたときや、WhereOrderBy などのメソッドで変更されたときには実行されません。 クエリの実行が先延ばしされるため、式の評価は、実現された値が実際に反復評価されるか ToList メソッドが呼び出される時点まで遅延されます。 SearchIndex サンプルの場合、クエリは SearchIndex ビューで実行されます。 クエリの遅延実行の詳細については、「クエリの実行」を参照してください。

以上で、フォームをユーザーに表示する SearchIndex ビューを実装できるようになりました。 SearchIndex メソッド内を右クリックし、[Add View (ビューの追加)] をクリックします。 [Add View (ビューの追加)] ダイアログ ボックスで、ビュー テンプレートに Movie オブジェクトをモデル クラスとして渡すことを指定します。 [スキャフォールディング テンプレート][リスト] を選択し、[追加] をクリックします。

AddSearchView

[Add (追加)] ボタンをクリックすると、Views\Movies\SearchIndex.vbhtml ビュー テンプレートが作成されます。 [スキャフォールディング テンプレート] の一覧で [リスト] を選択したため、Visual Web Developer によってビュー内に既定のコンテンツが自動生成 (スキャフォールディング) され、 HTML フォームができました。 これは、スキャフォールディングによって Movie クラスが分析され、クラス プロパティごとに <label> 要素をレンダリングするコードが作成されたためです。 生成された Create ビューの C# HTML コードを次に示します。

@ModelType IEnumerable(Of MvcMovie.Movie)

@Code
    ViewData("Title") = "SearchIndex"
End Code

<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>

@For Each item In Model
    Dim currentItem = item
    @<tr>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Title)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.ReleaseDate)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Genre)
        </td>
        <td>
            @Html.DisplayFor(Function(modelItem) currentItem.Price)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Details", "Details", New With {.id = currentItem.ID}) |
            @Html.ActionLink("Delete", "Delete", New With {.id = currentItem.ID})
        </td>
    </tr>
Next

</table>

アプリケーションを実行し、/Movies/SearchIndex に移動します。 ?searchString=ghost などのクエリ文字列を URL に追加します。 フィルターされたムービーが表示されます。

SearchQryStr

id という名前のパラメーターを使用するために SearchIndex メソッドのシグネチャを変更すると、id パラメーターは、Global.asax ファイルで設定されている既定ルートの {id} プレースホルダーと一致するようになります。

{controller}/{action}/{id}

変更後の SearchIndex メソッドは次のようになります。

Public Function SearchIndex(ByVal id As String) As ActionResult
Dim searchString As String = id
Dim movies = From m In db.Movies
             Select m

If Not String.IsNullOrEmpty(searchString) Then
    movies = movies.Where(Function(s) s.Title.Contains(searchString))
End If

Return View(movies)
End Function

これで、クエリ文字列の値ではなく、ルート データ (URL セグメント) として検索タイトルを渡すことができます。

SearchRouteData

ただし、ユーザーがムービーを検索するたびに URL の変更を求めることはできません。 そのため、映画をフィルター処理する UI を追加することにします。 ルート バインドされた ID パラメーターを渡す方法をテストするために SearchIndex メソッドのシグネチャを変更した場合は、SearchIndex メソッドが searchString という名前の文字列パラメーターを受け取るようにシグネチャを元に戻します。

Views\Movies\SearchIndex.vbhtml ファイルを開き、@Html.ActionLink("Create New", "Create") の直後に以下を追加します。

@Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code

Html.BeginForm ヘルパーは、開始 <form> タグを作成します。 [Filter (フィルター)] ボタンのクリック操作でフォームが送信されると、Html.BeginForm ヘルパーにより、フォームがそれ自体に送信されます。

アプリケーションを実行し、映画を検索してみてください。

SearchIndxIE9_title

SearchIndex メソッドの HttpPost オーバーロードはありません。 このメソッドはデータをフィルター処理するだけで、アプリケーションの状態を変更しないため、必要がないからです。 次の HttpPost SearchIndex メソッドを追加した場合、アクション呼び出し側は HttpPost SearchIndex メソッドと一致し、 HttpPost SearchIndex メソッドは次の図のように実行されます。

<HttpPost()>
 Public Function SearchIndex(ByVal fc As FormCollection, ByVal searchString As String) As String
     Return "<h3> From [HttpPost]SearchIndex: " & searchString & "</h3>"
 End Function

SearchPostGhost

ジャンル別検索を追加する

先ほど SearchIndex メソッドの HttpPost バージョンを追加した場合は、ここで削除してください。

次は、映画のジャンル別検索をユーザーに提供する機能を追加します。 SearchIndex メソッドを次のコードで置き換えます。

Public Function SearchIndex(ByVal movieGenre As String, ByVal searchString As String) As ActionResult
    Dim GenreLst = New List(Of String)()

    Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre
    GenreLst.AddRange(GenreQry.Distinct())
    ViewBag.movieGenre = New SelectList(GenreLst)

    Dim movies = From m In db.Movies
                 Select m

    If Not String.IsNullOrEmpty(searchString) Then
        movies = movies.Where(Function(s) s.Title.Contains(searchString))
    End If

    If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

End Function

SearchIndex メソッドのこのバージョンは、movieGenre という名前の追加のパラメーターを受け取ります。 このコードの先頭部分では、データベースから映画のジャンル情報を取得して保持するための List オブジェクトを作成しています。

次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。

Dim GenreQry = From d In db.Movies
                   Order By d.Genre
                   Select d.Genre

このコードでは、ジェネリックな List コレクションの AddRange メソッドを使用して、異なる映画ジャンル名をすべてリストに追加します (もし Distinct 修飾子を指定しないと、同じジャンルが複数回出現することになります。たとえば、このサンプルではコメディが 2 つになります)。 その後、ジャンルのリストを ViewBag オブジェクトに格納します。

以下のコードは、movieGenre パラメーターを検査する方法を示しています。 値が空でない場合にはクエリの絞り込みを厳しくし、指定のジャンルに合った映画だけを検索するようにします。

If String.IsNullOrEmpty(movieGenre) Then
        Return View(movies)
    Else
        Return View(movies.Where(Function(x) x.Genre = movieGenre))
    End If

SearchIndex ビューにマークアップを追加してジャンル別の検索をサポートする

Views\Movies\SearchIndex.vbhtml ファイル内の TextBox ヘルパーの直前に Html.DropDownList ヘルパーを追加します。 完成したマークアップを以下に示します。

<p>
    @Html.ActionLink("Create New", "Create")
    @Code
    ViewData("Title") = "SearchIndex"
    Using (Html.BeginForm())
         @<p> Genre: @Html.DropDownList("movieGenre", "All")
         Title: @Html.TextBox("SearchString") 
         <input type="submit" value="Filter" /></p>
        End Using
End Code
</p>

アプリケーションを実行し、/Movies/SearchIndex を参照します。 ジャンル、映画名、および両方の条件で検索してみてください。

このセクションでは、フレームワークによって生成される CRUD のアクション メソッドとビューについて説明しました。 ユーザーにタイトルとジャンルでの映画検索機能を提供する検索アクション メソッドとビューを作成しました。 次のセクションでは、Movie モデルにプロパティを追加する方法と、テスト データベースを自動的に作成する初期化子を追加する方法について説明します。