作者: 里克·安德森
在解决方案资源管理器中,右键单击“控制器”文件夹,然后选择“添加控制器”。 将控制器 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>流派外键的元素显示可能的流派名称列表,但保存窗体时,流派属性将更新为流派外键值,而不是显示的流派名称。 在下图中,选择的流派是 迪斯科 ,艺术家是 唐娜·萨默。

检查 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
)
- 项: 包含列表中的项的 IEnumerable 。 在上面的示例中,返回
db.Genres的流派列表。 - dataValueField:包含键值的 IEnumerable 列表中的属性的名称。 在上面的示例中,
GenreId和ArtistId. - 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 模型绑定用于创建专辑对象。
。
重构 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由于命名GenreId了SelectList该属性,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 对象名称更改为Genres和 Artists。 已完成的代码如下所示:
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。 更改字段并点击 “保存 ”按钮以验证代码是否正常工作。

应使用哪种方法?
显示的所有三种方法都是可接受的。 许多开发人员宁愿显式传递给SelectListDropDownList使用 ViewBag。 此方法具有额外的优势,使你可以灵活地为集合使用更合适的名称。 一个注意事项是不能将 ViewBag SelectList 对象命名为与模型属性相同的名称。
某些开发人员更喜欢 ViewModel 方法。 另一些人则认为 ViewModel 的更详细标记和生成的 HTML 方法处于劣势。
在本部分中,我们学习了将 DropDownList 与类别数据配合使用的三种方法。 在下一部分中,我们将演示如何添加新类别。