检查 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>
流派外键的元素显示可能的流派名称列表,但保存窗体时,流派属性将更新为流派外键值,而不是显示的流派名称。 在下图中,选择的流派是 迪斯科 ,艺术家是 唐娜·萨默。
检查 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 创建
方法和Edit
HTTP 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 POST
Edit
操作。
运行应用程序并编辑相册。 更改要使用的 EditVM
URL。 更改字段并点击 “保存 ”按钮以验证代码是否正常工作。
应使用哪种方法?
显示的所有三种方法都是可接受的。 许多开发人员宁愿显式传递给SelectList
DropDownList
使用 ViewBag
。 此方法具有额外的优势,使你可以灵活地为集合使用更合适的名称。 一个注意事项是不能将 ViewBag SelectList
对象命名为与模型属性相同的名称。
某些开发人员更喜欢 ViewModel 方法。 另一些人则认为 ViewModel 的更详细标记和生成的 HTML 方法处于劣势。
在本部分中,我们学习了将 DropDownList 与类别数据配合使用的三种方法。 在下一部分中,我们将演示如何添加新类别。