检查 ASP.NET MVC 如何支持 DropDownList 帮助程序

作者: 里克·安德森

解决方案资源管理器中,右键单击“控制器”文件夹,然后选择“添加控制器”。 将控制器 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 运行应用程序。 选择“ 管理 ”链接,然后选择“ 新建 ”链接以创建新相册。 验证是否已保存专辑信息。 编辑相册并验证所做的更改是否持久化。

专辑架构

StoreManager MVC 基架机制创建的控制器允许 CRUD(创建、读取、更新、删除)访问音乐存储数据库中的专辑。 专辑信息的架构如下所示:

相册架构的图像

Albums 表不存储专辑流派和说明,它存储表的 Genres 外键。 该 Genres 表包含流派名称和说明。 同样,该 Albums 表格不包含专辑艺术家名称,而是表格的 Artists 外键。 该 Artists 表包含艺术家的姓名。 如果检查表中的数据Albums,可以看到每一行都包含表的外键和表的Artists外键Genres。 下图显示了表中的一些表数据 Albums

相册表中某些数据的图像

HTML 选择标记

HTML 元素(由 HTML <select> DropDownList 帮助程序创建)用于显示值的完整列表(如流派列表)。 对于编辑表单,当当前值已知时,选择列表可以显示当前值。 我们之前在将所选值设置为 喜剧时看到了这一点。 选择列表非常适合用于显示类别或外键数据。 <select>流派外键的元素显示可能的流派名称列表,但保存窗体时,流派属性将更新为流派外键值,而不是显示的流派名称。 在下图中,选择的流派是 迪斯科 ,艺术家是 唐娜·萨默

Disco 所选流派的图像

检查 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 将两 个 SelectList 对象添加到其中 ViewBag,一个对象包含流派信息,一个用于包含艺术家信息。 上面使用的 SelectList 构造函数重载采用三个参数:

public SelectList(

    IEnumerable items,

    string dataValueField,

    string dataTextField

)
  1. 包含列表中的项的 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。 第二个参数 (String.Empty) 是在未选择任何项时显示的文本。 这正是创建新专辑时我们想要的。 如果删除了第二个参数并使用了以下代码:

@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 重载采用 album 由发布表单值中的 ASP.NET MVC 模型绑定系统创建的对象。 提交新相册时,如果模型状态有效且没有数据库错误,则新相册将添加数据库。 下图显示了新专辑的创建。

显示创建新专辑的图像

可以使用 fiddler 工具 检查发布表单值,这些值 ASP.NET MVC 模型绑定用于创建专辑对象。

Fiddler 工具的图像

重构 ViewBag SelectList 创建

方法和EditHTTP POST Create方法都具有相同的代码,用于在 ViewBag设置 SelectList。 在 DRY 的精神中,我们将重构此代码。 稍后我们将使用此重构的代码。

创建一个新方法,将流派和艺术家 SelectList 添加到 ViewBag

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

}

将每个Create行和Edit方法中的两行ViewBag替换为对该方法的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)

ViewBag由于命名GenreIdSelectList该属性,DropDownList 帮助程序将使用GenreId ViewBag 中的 SelectList。 在以下 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。 针对“流派”类别执行此操作。 已完成的代码如下所示:

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

运行应用程序并单击“管理员链接,然后导航到爵士专辑并选择“编辑”链接。

要编辑的爵士专辑选择的图像

将显示 Rock,而不是将 Jazz 显示为当前选定的流派。 当字符串参数(要绑定的属性)和 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。 我们之前所做的重构得到了回报。 我们的更改不是在四种方法中更改 ViewBag ,而是与 SetGenreArtistViewBag 该方法隔离。

更改 创建和编辑视图中的 DropDownList 调用,以使用新的 SelectList 名称。 编辑视图的新标记如下所示:

<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 帮助程序配合使用

在 ViewModels 文件夹中创建名为 AlbumSelectListViewModel 的新类。 将 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传递了容器,而不是 Album 视图中的 Edit 容器。
  • DropDownList 第二个参数来自视图模型,而不是 ViewBag
  • 视图中的 BeginForm 帮助程序 EditVM 显式发回 Edit 操作方法。 通过回发到Edit操作,我们不必编写操作HTTP POST EditVM,并且可以重复使用该HTTP POSTEdit操作。

运行应用程序并编辑相册。 更改要使用的 EditVMURL。 更改字段并点击 “保存 ”按钮以验证代码是否正常工作。

U R L 更改为“编辑 V M”的图像

应使用哪种方法?

显示的所有三种方法都是可接受的。 许多开发人员宁愿显式传递给SelectListDropDownList使用 ViewBag。 此方法具有额外的优势,使你可以灵活地为集合使用更合适的名称。 一个注意事项是不能将 ViewBag SelectList 对象命名为与模型属性相同的名称。

某些开发人员更喜欢 ViewModel 方法。 另一些人则认为 ViewModel 的更详细标记和生成的 HTML 方法处于劣势。

在本部分中,我们学习了将 DropDownList 与类别数据配合使用的三种方法。 在下一部分中,我们将演示如何添加新类别。