ASP.NET MVC で DropDownList ヘルパーをスキャフォールディングするしくみを確認する

作成者: Rick Anderson

ソリューション エクスプローラーで、Controllers フォルダーを右クリックし、[コントローラーの追加] を選択します。 コントローラーに StoreManagerController という名前を付けます。 次の図に示すように、[コントローラーの追加] ダイアログのオプションを設定します。

ソリューション エクスプローラー [コントローラーの追加] ダイアログ ボックスの画像

StoreManager\Index.cshtml ビューを編集して AlbumArtUrl を削除します。 AlbumArtUrl を削除すると、プレゼンテーションが読みやすくなります。 完成したコードを以下に示します。

@model IEnumerable<MvcMusicStore.Models.Album>

@{

    ViewBag.Title = "Index";

}

<h2>Index</h2>

<p>

    @Html.ActionLink("Create New", "Create")

</p>

<table>

    <tr>

        <th>

            Genre

        </th>

        <th>

            Artist

        </th>

        <th>

            Title

        </th>

        <th>

            Price

        </th>

        <th></th>

    </tr>

@foreach (var item in Model) {

    <tr>

        <td>

            @Html.DisplayFor(modelItem => item.Genre.Name)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Artist.Name)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Title)

        </td>

        <td>

            @Html.DisplayFor(modelItem => item.Price)

        </td>

        <td>

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

            @Html.ActionLink("Details", "Details", new { id=item.AlbumId }) |

            @Html.ActionLink("Delete", "Delete", new { id=item.AlbumId })

        </td>

    </tr>

}

</table>

Controllers\StoreManagerController.cs ファイルを開き、Index メソッドを見つけます。 OrderBy 句を追加して、アルバムを価格順に並べ替えます。 完全なコードは次のとおりです。

public ViewResult Index()
{

    var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist)

        .OrderBy(a => a.Price);

    return View(albums.ToList());

}

価格で並べ替えると、データベースの変更を簡単にテストできます。 編集メソッドと作成メソッドをテストする場合は、低コストを使用して、保存されたデータが最初に表示されるようにすることができます。

StoreManager\Edit.cshtml ファイルを開きます。 凡例タグの直後に次の行を追加します。

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

次のコードは、この変更のコンテキストを示しています。

@using (Html.BeginForm()) {

    @Html.ValidationSummary(true)

    <fieldset>

        <legend>Album</legend>

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

        <div class="editor-label">

            @Html.LabelFor(model => model.GenreId, "Genre")

        </div>

        <div class="editor-field">

            @Html.DropDownList("GenreId", String.Empty)

            @Html.ValidationMessageFor(model => model.GenreId)

        </div>

        <!-- Items removed for brevity. -->

}

アルバム レコードを変更するには、AlbumId が必要です。

Ctrl キーを押しながら F5 キーを押してアプリケーションを実行します。 [管理] リンクを選択し、[新規作成] リンクを選択して新しいアルバムを作成します。 アルバム情報が保存されたことを確認します。 アルバムを編集し、加えた変更が保持されていることを確認します。

アルバム スキーマ

MVC スキャフォールディング メカニズムによって作成された StoreManager コントローラーにより、Music Store データベース内のアルバムへの CRUD (作成、読み取り、更新、削除) アクセスが許可されます。 アルバム情報のスキーマを次に示します。

アルバム スキーマの画像

Albums テーブルは、アルバムのジャンルと説明は格納せず、Genres テーブルへの外部キーを格納します。 Genres テーブルはジャンル名と説明を格納します。 同様に、Albums テーブルは、アルバム アーティスト名ではなく、Artists テーブルへの外部キーを格納します。 Artists テーブルはアーティストの名前を格納します。 Albums テーブル内のデータを調べると、各行に Genres テーブルへの外部キーと、Artists テーブルへの外部キーが含まれていることがわかります。 次の図は、Albums テーブルのテーブル データの一部を示しています。

Albums テーブルの一部のデータの画像

HTML Select タグ

HTML <select> 要素 (HTML DropDownList ヘルパーで作成) は、値の完全な一覧 (ジャンルの一覧など) を表示するために使用されます。 編集フォームについては、現在の値がわかっている場合は、選択リストに現在の値を表示できます。 これは、選択した値をコメディに設定したときに確認しました。 選択リストは、カテゴリまたは外部キー データを表示するのに最適です。 Genre 外部キーの <select> 要素には使用可能なジャンル名の一覧が表示されますが、フォームを保存すると、Genre プロパティは、表示されるジャンル名ではなく、Genre 外部キーの値で更新されます。 下の画像では、選択されたジャンルは Disco で、アーティストは Donna Summer です。

選択されたジャンルのディスコの画像

ASP.NET MVC スキャフォールディング コードの確認

Controllers\StoreManagerController.cs ファイルを開き、HTTP GET Create メソッドを見つけます。

public ActionResult Create()

{

    ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");

    ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");

    return View();

}

Create メソッドは、2 つの SelectList オブジェクト (ジャンル情報を格納するものとアーティスト情報を含むもの) を ViewBag に追加します。 上記で使用した SelectList コンストラクター オーバーロードは、次の 3 つの引数を取ります。

public SelectList(

    IEnumerable items,

    string dataValueField,

    string dataTextField

)
  1. items: リスト内の項目を含む IEnumerable。 上記の例では、db.Genres によって返されるジャンルの一覧です。
  2. dataValueField: キー値を含む IEnumerable リスト内のプロパティの名前。 上の例では GenreIdArtistId です。
  3. dataTextField: 表示する情報を含む IEnumerable リスト内のプロパティの名前。 アーティストとジャンルの両方のテーブルで、name フィールドが使用されます。

Views\StoreManager\Create.cshtml ファイルを開き、ジャンル フィールドの Html.DropDownList ヘルパー マークアップを調べます。

@model MvcMusicStore.Models.Album

@*        Markup removed for clarity.*@

@Html.DropDownList("GenreId", String.Empty)

最初の行は、作成ビューが Album モデルを取っていることを示しています。 上記の Create メソッドでは、モデルが渡されていないため、ビューは null Album モデルを取得します。 この時点で、新しいアルバムを作成しているので、Album データはありません。

上記の Html.DropDownList オーバーロードは、モデルにバインドするフィールドの名前を取ります。 また、この名前を使用して、SelectList オブジェクトを含む ViewBag オブジェクトを検索します。 このオーバーロードを使用するには、ViewBag SelectList オブジェクトに GenreId という名前を付ける必要があります。 2 番目のパラメーター (String.Empty) は、項目が選択されていないときに表示するテキストです。 これは、新しいアルバムを作成するときにまさに求めているものです。 2 番目のパラメーターを削除し、次のコードを使用した場合:

@Html.DropDownList("GenreId")

選択リストは、既定で最初の要素 (サンプルの Rock) になります。

既定の最初の要素の画像

HTTP POST Create メソッドを調べます。

//

// POST: /StoreManager/Create

[HttpPost]

public ActionResult Create(Album album)

{

    if (ModelState.IsValid)

    {

        db.Albums.Add(album);

        db.SaveChanges();

        return RedirectToAction("Index");  

    }

    ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name",

        album.GenreId);

    ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name",

        album.ArtistId);

    return View(album);

}

Create メソッドのこのオーバーロードは、ポストされたフォーム値から、ASP.NET MVC モデル バインド システムによって作成された album オブジェクトを取ります。 新しいアルバムを送信するときに、モデルの状態が有効で、データベース エラーがない場合は、新しいアルバムがデータベースに追加されます。 次の図は、新しいアルバムの作成を示しています。

新しいアルバムの作成を示す画像

fiddler ツールを使用すると、ASP.NET MVC モデル バインドがアルバム オブジェクトの作成に使用する、ポストされたフォーム値を調べることができます。

Fiddler ツールの画像.

ViewBag SelectList 作成のリファクタリング

Edit メソッドと HTTP POST Create メソッドのどちらにも、ViewBagSelectList を設定するための同じコードがあります。 DRY の考えに基づき、このコードをリファクタリングします。 このリファクタリングされたコードは後で使用します。

ジャンルとアーティストの SelectListViewBag に追加する新しいメソッドを作成します。

private void SetGenreArtistViewBag(int? GenreID = null, int? ArtistID = null) {

    if (GenreID == null)

        ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");

    else

        ViewBag.GenreId = new SelectList(db.Genres.ToArray(), "GenreId", "Name", GenreID);

    if (ArtistID == null)

        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");

    else

        ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", ArtistID);

}

CreateEdit メソッドでそれぞれ ViewBag を設定している 2 つの行を、SetGenreArtistViewBag メソッドの呼び出しに置き換えます。 完成したコードを以下に示します。

//

// GET: /StoreManager/Create

public ActionResult Create() {

    SetGenreArtistViewBag();

    return View();

}

//

// POST: /StoreManager/Create

[HttpPost]

public ActionResult Create(Album album) {

    if (ModelState.IsValid) {

        db.Albums.Add(album);

        db.SaveChanges();

        return RedirectToAction("Index");

    }

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

//

// GET: /StoreManager/Edit/5

public ActionResult Edit(int id) {

    Album album = db.Albums.Find(id);

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

//

// POST: /StoreManager/Edit/5

[HttpPost]

public ActionResult Edit(Album album) {

    if (ModelState.IsValid) {

        db.Entry(album).State = EntityState.Modified;

        db.SaveChanges();

        return RedirectToAction("Index");

    }

    SetGenreArtistViewBag(album.GenreId, album.ArtistId);

    return View(album);

}

新しいアルバムを作成し、アルバムを編集して変更が機能することを確認します。

SelectList を DropDownList に明示的に渡す

ASP.NET MVC スキャフォールディングによって作成されたビューの作成と編集には、次の DropDownList オーバーロードが使用されます。

public static MvcHtmlString DropDownList(

    this HtmlHelper htmlHelper,

    string name,         // The name of the ViewModel property to bind.

    string optionLabel   // The string added to the top of the list

                         // typically  String.Empty or "Select a Genre"

)

作成ビューの DropDownList マークアップを次に示します。

@Html.DropDownList("GenreId", String.Empty)

SelectListViewBag プロパティには GenreId という名前が付けられているため、DropDownList ヘルパーは ViewBagGenreIdSelectList を使用します。 次の DropDownList オーバーロードでは、SelectList が明示的に渡されます。

public static MvcHtmlString DropDownList(

    this HtmlHelper htmlHelper,

    string name,            // The name of the ViewModel property to bind.

    IEnumerable selectList  // The SelectList

)

Views\StoreManager\Edit.cshtml ファイルを開き、上記のオーバーロードを使用して、DropDownList 呼び出しを SelectList に明示的に渡すように変更します。 Genre カテゴリに対してこれを行います。 完成したコードを以下に示します。

@Html.DropDownList("GenreId", ViewBag.GenreId as SelectList)

アプリケーションを実行して [管理] リンクをクリックし、Jazz アルバムに移動して [編集] リンクを選択します。

編集するジャズアルバム選択の画像

現在選択されているジャンルとして、Jazz の代わりに Rock が表示されます。 文字列引数 (バインドするプロパティ) と SelectList オブジェクトの名前が同じである場合、選択した値は使用されません。 選択した値がない場合、ブラウザーは既定で SelectList の最初の要素 (上の例では Rock) に設定されます。 これは、DropDownList ヘルパーの既知の制限です。

Controllers\StoreManagerController.cs ファイルを開き、SelectList オブジェクトの名前を GenresArtists に変更します。 完成したコードを以下に示します。

private void SetGenreArtistViewBag(int? GenreID = null, int? ArtistID = null) {
    if (GenreID == null)

        ViewBag.Genres = new SelectList(db.Genres, "GenreId", "Name");

    else

        ViewBag.Genres = new SelectList(db.Genres.ToArray(), "GenreId", "Name", GenreID);

    if (ArtistID == null)

        ViewBag.Artists = new SelectList(db.Artists, "ArtistId", "Name");

    else

        ViewBag.Artists = new SelectList(db.Artists, "ArtistId", "Name", ArtistID);

}

カテゴリの名前としては、各カテゴリの ID 以上のものを含む Genres と Artists が適しています。 先ほど行ったリファクタリングの成果が得られました。 4 つのメソッドで ViewBag を変更する代わりに、変更は SetGenreArtistViewBag メソッドに分離されました。

新しい SelectList 名を使用するように、作成ビューと編集ビューの DropDownList 呼び出しを変更します。 編集ビューの新しいマークアップを次に示します。

<div class="editor-label">

    @Html.LabelFor(model => model.GenreId, "Genre")

</div>

<div class="editor-field">

    @Html.DropDownList("GenreId", ViewBag.Genres as SelectList)

    @Html.ValidationMessageFor(model => model.GenreId)

</div>

<div class="editor-label">

    @Html.LabelFor(model => model.ArtistId, "Artist")

</div>

<div class="editor-field">

    @Html.DropDownList("ArtistId", ViewBag.Artists as SelectList)

    @Html.ValidationMessageFor(model => model.ArtistId)

</div>

作成ビューには、SelectList の最初の項目が表示されないようにするために空の文字列が必要です。

<div class="editor-label">

    @Html.LabelFor(model => model.GenreId, "Genre" )

</div>

<div class="editor-field">

    @Html.DropDownList("GenreId", ViewBag.Genres as SelectList, String.Empty)

    @Html.ValidationMessageFor(model => model.GenreId)

</div>

<div class="editor-label">

    @Html.LabelFor(model => model.ArtistId, "Artist")

</div>

<div class="editor-field">

    @Html.DropDownList("ArtistId", ViewBag.Artists as SelectList, String.Empty)

    @Html.ValidationMessageFor(model => model.ArtistId)

</div>

新しいアルバムを作成し、アルバムを編集して変更が機能することを確認します。 Rock 以外のジャンルのアルバムを選択して、編集コードをテストします。

DropDownList ヘルパーでのビュー モデルの使用

AlbumSelectListViewModel という名前の ViewModels フォルダーに新しいクラスを作成します。 AlbumSelectListViewModel クラスのコードを、次に置き換えます。

using MvcMusicStore.Models;

using System.Web.Mvc;

using System.Collections;

namespace MvcMusicStore.ViewModels {

    public class AlbumSelectListViewModel {

        public Album Album { get; private set; }

        public SelectList Artists { get; private set; }

        public SelectList Genres { get; private set; }

        public AlbumSelectListViewModel(Album album, 

                                        IEnumerable artists, 

                                        IEnumerable genres) {

            Album = album;

            Artists = new SelectList(artists, "ArtistID", "Name", album.ArtistId);

            Genres = new SelectList(genres, "GenreID", "Name", album.GenreId);

        }

    }

}

AlbumSelectListViewModel コンストラクターは、アルバム (アーティストとジャンルのリスト) を取得し、アルバムと、ジャンルとアーティストを含む SelectList オブジェクトを作成します。

次の手順でビューを作成するときに AlbumSelectListViewModel 使用できるようにプロジェクトをビルドします。

EditVM メソッドを StoreManagerController に追加します。 完成したコードを以下に示します。

//

// GET: /StoreManager/EditVM/5

public ActionResult EditVM(int id) {

    Album album = db.Albums.Find(id);

    if (album == null)

        return HttpNotFound();

    AlbumSelectListViewModel aslvm = new AlbumSelectListViewModel(album, db.Artists, db.Genres);

    return View(aslvm);

}

AlbumSelectListViewModel を右クリックして [解決] を選択し、[MvcMusicStore.ViewModels; を使用] を選択します。

イメージの選択の解決

または、次の using ステートメントを追加することもできます。

using MvcMusicStore.ViewModels;

EditVM を右クリックして [ビューの追加] を選択します。 次に示すオプションを使用します。

[ビューの追加] ダイアログを示す画像

[追加] を選択し、Views\StoreManager\EditVM.cshtml ファイルの内容を次のように置き換えます。

@model MvcMusicStore.ViewModels.AlbumSelectListViewModel

@{

    ViewBag.Title = "EditVM";

}

<h2>Edit VM</h2>

@using (Html.BeginForm("Edit","StoreManager",FormMethod.Post)) {

    @Html.ValidationSummary(true)

    <fieldset>

        <legend>Album</legend>

        @Html.HiddenFor(model => model.Album.AlbumId )

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.GenreId, "Genre")

        </div>

        <div class="editor-field">

            @Html.DropDownList("Album.GenreId", Model.Genres)

            @Html.ValidationMessageFor(model => model.Album.GenreId)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.ArtistId, "Artist")

        </div>

        <div class="editor-field">

            @Html.DropDownList("Album.ArtistId", Model.Artists)

            @Html.ValidationMessageFor(model => model.Album.ArtistId)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.Title)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.Title)

            @Html.ValidationMessageFor(model => model.Album.Title)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.Price)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.Price)

            @Html.ValidationMessageFor(model => model.Album.Price)

        </div>

        <div class="editor-label">

            @Html.LabelFor(model => model.Album.AlbumArtUrl)

        </div>

        <div class="editor-field">

            @Html.EditorFor(model => model.Album.AlbumArtUrl)

            @Html.ValidationMessageFor(model => model.Album.AlbumArtUrl)

        </div>

        <p>

            <input type="submit" value="Save" />

        </p>

    </fieldset>

}

<div>

    @Html.ActionLink("Back to List", "Index")

</div>

EditVM マークアップは元の Edit マークアップによく似ていますが、次の例外があります。

  • Edit ビュー内のモデル プロパティは model.property という形式 (model.Title など) です。 EditVm ビュー内のモデル プロパティは model.Album.property という形式 (model.Album.Title など) です。 これは、EditVM ビューが Album ビューの Edit のようにではなく、Album のコンテナーとして渡されるためです。
  • DropDownList の 2 番目のパラメーターは、ViewBag ではなくビュー モデルから取得されます。
  • EditVM ビューの BeginForm ヘルパーは、Edit アクション メソッドに明示的にポストバックします。 Editアクションにポストバックすることで、HTTP POST EditVMアクションを記述する必要がなく、HTTP POST Editアクションを再利用できます。

アプリケーションを実行し、アルバムを編集します。 EditVM を使用するように URL を変更します。 フィールドを変更し、[保存] ボタンをクリックしてコードが動作していることを確認します。

U R L が [Edit V M] に変更された画像

どの手法を使うべきか

提示されている 3 つの手法はどれも使用できます。 多くの開発者が好むのは、ViewBag を使用して DropDownListSelectList を明示的に渡すことです。 この方法により、コレクションに適切な名前を使用する柔軟性が得られるという利点が生まれます。 1 つの注意点は、モデル プロパティと同じ名前を ViewBag SelectList オブジェクトに付けることができないことです。

一部の開発者は ViewModel による手法を好みます。 別のユーザーは、ViewModel 手法での、より詳しいマークアップと生成された HTML を欠点と捉えます。

このセクションでは、カテゴリ データを使って DropDownList を使用する 3 つの手法について説明しました。 次のセクションでは、新しいカテゴリを追加する方法について説明します。