Examinar cómo ASP.NET MVC agrega un scaffold al asistente DropDownList
Por Rick Anderson
En el Explorador de soluciones, haga clic con el botón derecho en la carpeta Controllers y, después, seleccione Agregar controlador. Asigne al controlador el nombre StoreManagerController. Establezca las opciones del cuadro de diálogo Agregar controlador como se muestra en la imagen siguiente.
Edite la vista StoreManager\Index.cshtml y quite AlbumArtUrl
. Quitar AlbumArtUrl
hará que la presentación sea más legible. A continuación se muestra el código completado.
@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>
Abra el archivo Controllers\StoreManagerController.cs y busque el método Index
. Agregue la cláusula OrderBy
para que los álbumes se ordenen por precio. A continuación se muestra el código completo.
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist)
.OrderBy(a => a.Price);
return View(albums.ToList());
}
La ordenación por precio permitirá probar los cambios fácilmente en la base de datos. Al probar los métodos de edición y creación, puede usar un precio bajo para que los datos guardados aparezcan primero.
Abra el archivo StoreManager\Edit.cshtml. Agregue la siguiente línea justo después de la etiqueta de leyenda.
@Html.HiddenFor(model => model.AlbumId)
El código siguiente muestra el contexto de este cambio:
@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. -->
}
El elemento AlbumId
es necesario para realizar cambios en un álbum.
Presione CTRL+F5 para ejecutar la aplicación. Seleccione el vínculo Admin y, después, seleccione el vínculo Create new para crear un nuevo álbum. Compruebe que se ha guardado la información del álbum. Edite un álbum y compruebe que los cambios realizados se conservan.
Esquema del álbum
El controlador StoreManager
creado por el mecanismo de scaffolding de MVC proporciona acceso para crear, leer, actualizar y eliminar (acceso CRUD) a los álbumes de la base de datos de la tienda de música. A continuación se muestra el esquema de la información del álbum:
La tabla Albums
no almacena el género y la descripción del álbum, almacena una clave externa a la tabla Genres
. La tabla Genres
contiene el nombre y la descripción del género. Del mismo modo, la tabla Albums
no contiene el nombre de los artistas del álbum, sino una clave externa a la tabla Artists
. La tabla Artists
contiene el nombre de los artistas. Si examina los datos de la tabla Albums
, puede ver que cada fila contiene una clave externa a la tabla Genres
y otra a la tabla Artists
. En la imagen siguiente se muestran algunos datos de la tabla Albums
.
Etiqueta de selección de HTML
El elemento HTML <select>
(creado por el asistente DropDownList de HTML) se usa para mostrar una lista completa de valores (como la lista de géneros). En el caso de los formularios de edición, cuando se conoce el valor actual, la lista de selección puede mostrar el valor actual. Vimos esto anteriormente cuando establecimos el valor seleccionado en Comedy. La lista de selección es ideal para mostrar datos de categoría o de clave externa. El elemento <select>
de la clave externa Genre muestra la lista de posibles nombres de género, pero al guardar el formulario, la propiedad Genre se actualiza con el valor de la clave externa Genre, no con el nombre de género mostrado. En la imagen siguiente, el género seleccionado es Disco y el artista es Donna Summer.
Examen del código con scaffolding de ASP.NET MVC
Abra el archivo Controllers\StoreManagerController.cs y busque el método HTTP GET Create
.
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
El método Create
agrega dos objetos SelectList a ViewBag
, uno para contener la información del género y otro para contener la información del artista. La sobrecarga del constructor SelectList usada anteriormente toma tres argumentos:
public SelectList(
IEnumerable items,
string dataValueField,
string dataTextField
)
- Items: un objeto IEnumerable que contiene los elementos de la lista. En el ejemplo anterior, la lista de géneros devueltos por
db.Genres
. - dataValueField: el nombre de la propiedad en la lista de IEnumerable que contiene el valor de clave. En el ejemplo anterior,
GenreId
yArtistId
. - dataTextField: el nombre de la propiedad en la lista de IEnumerable que contiene la información que se va a mostrar. En la tabla de artistas y géneros, se usa el campo
name
.
Abra el archivo Views\StoreManager\Create.cshtml y examine el marcado del asistente Html.DropDownList
para el campo de género.
@model MvcMusicStore.Models.Album
@* Markup removed for clarity.*@
@Html.DropDownList("GenreId", String.Empty)
La primera línea muestra que la vista de creación toma un modelo Album
. En el método Create
que se muestra anteriormente, no se pasa ningún modelo, por lo que la vista obtiene un modelo Album
null. En este momento estamos creando un nuevo álbum para el que no tenemos datos de Album
.
La sobrecarga de Html.DropDownList que se muestra anteriormente toma el nombre del campo que se va a enlazar al modelo. También usa este nombre para buscar un objeto ViewBag que contenga un objeto SelectList. Con esta sobrecarga, debe asignar al objeto ViewBag SelectList el nombre GenreId
. El segundo parámetro (String.Empty
) es el texto que se muestra cuando no se selecciona ningún elemento. Esto es exactamente lo que queremos al crear un nuevo álbum. Quite el segundo parámetro y use el código siguiente:
@Html.DropDownList("GenreId")
Al hacerlo, la lista de selección tendrá como valor predeterminado el primer elemento, Rock en nuestro ejemplo.
Examine el método 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);
}
Esta sobrecarga del método Create
toma un objeto album
, creado por el sistema de enlace de modelos de ASP.NET MVC a partir de los valores de formulario publicados. Al crear un nuevo álbum, si el estado del modelo es válido y no hay errores de base de datos, el nuevo álbum se agrega a la base de datos. En la imagen siguiente se muestra la creación de un nuevo álbum.
Puede usar la herramienta fiddler para examinar los valores de formulario publicados que usa el enlace de modelos de ASP.NET MVC para crear el objeto album.
.
Refactorización de la creación de SelectList en ViewBag
Tanto los métodos Edit
como el método HTTP POST Create
tienen código idéntico para configurar el objeto SelectList en ViewBag. Siguiendo la filosofía DRY, refactorizaremos este código. Más adelante usaremos este código refactorizado.
Cree un nuevo método para agregar una lista de selección (SelectList) para el género y el artista a 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);
}
Reemplace las dos líneas que establecen ViewBag
en cada uno de los métodos Create
y Edit
por una llamada al método SetGenreArtistViewBag
. A continuación se muestra el código completado.
//
// 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);
}
Cree un nuevo álbum y edite un álbum para comprobar que los cambios funcionan.
Paso de SelectList a DropDownList explícitamente
Las vistas de creación y edición creadas por el scaffolding de ASP.NET MVC usan la siguiente sobrecarga de 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"
)
A continuación se muestra el marcado DropDownList
de la vista de creación.
@Html.DropDownList("GenreId", String.Empty)
Dado que la propiedad ViewBag
de SelectList
se denomina GenreId
, el asistente DropDownList usará GenreId
como SelectList en ViewBag. En la siguiente sobrecarga de DropDownList, SelectList
se pasa explícitamente.
public static MvcHtmlString DropDownList(
this HtmlHelper htmlHelper,
string name, // The name of the ViewModel property to bind.
IEnumerable selectList // The SelectList
)
Abra el archivo Views\StoreManager\Edit.cshtml y cambie la llamada a DropDownList para pasar explícitamente el objeto SelectList mediante la sobrecarga anterior. Haga esto para la categoría de género. A continuación se muestra el código completado:
@Html.DropDownList("GenreId", ViewBag.GenreId as SelectList)
Ejecute la aplicación y haga clic en el vínculo Admin; después, vaya a un álbum de jazz y seleccione el vínculo Edit.
En lugar de mostrar Jazz como el género seleccionado actualmente, se muestra Rock. Cuando el argumento de cadena (la propiedad que se va a enlazar) y el objeto SelectList tienen el mismo nombre, no se usa el valor seleccionado. Cuando no se proporciona ningún valor seleccionado, los exploradores tienen como valor predeterminado el primer elemento de SelectList (que es Rock en el ejemplo anterior). Se trata de una limitación conocida del asistente DropDownList.
Abra el archivo Controllers\StoreManagerController.cs y cambie los nombres de objeto SelectList a Genres
y Artists
. A continuación se muestra el código completado:
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);
}
Los nombres Genres y Artists son mejores nombres para las categorías, ya que no solo contienen el identificador de cada categoría. La refactorización que hemos hecho anteriormente ha venido bien. En lugar de cambiar ViewBag en cuatro métodos, los cambios se han aislado en el método SetGenreArtistViewBag
.
Cambie la llamada a DropDownList en las vistas de creación y edición para usar los nuevos nombres de SelectList. A continuación se muestra el nuevo marcado de la vista de edición:
<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>
La vista de creación requiere una cadena vacía para evitar que se muestre el primer elemento de 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>
Cree un nuevo álbum y edite un álbum para comprobar que los cambios funcionan. Pruebe el código de edición seleccionando un álbum con un género distinto de Rock.
Uso de un modelo de vista con el asistente DropDownList
Cree una nueva clase en la carpeta ViewModels denominada AlbumSelectListViewModel
. Reemplace el código de la clase AlbumSelectListViewModel
por lo siguiente:
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);
}
}
}
El constructor AlbumSelectListViewModel
toma un álbum, una lista de artistas y géneros y crea un objeto que contiene el álbum y un objeto SelectList
para los géneros y los artistas.
Compile el proyecto para que AlbumSelectListViewModel
esté disponible cuando creemos una vista en el paso siguiente.
Agregue un método EditVM
a StoreManagerController
. A continuación se muestra el código completado.
//
// 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);
}
Haga clic con el botón derecho en AlbumSelectListViewModel
, seleccione Resolver y, después, using MvcMusicStore.ViewModels;.
De forma alternativa, puede agregar la siguiente instrucción using:
using MvcMusicStore.ViewModels;
Haga clic con el botón derecho en EditVM
y seleccione Agregar vista. Use las opciones que se muestran a continuación.
Seleccione Agregar y reemplace el contenido del archivo Views\StoreManager\EditVM.cshtml por lo siguiente:
@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>
El marcado de EditVM
es muy similar al marcado de Edit
original con las siguientes excepciones.
- Las propiedades del modelo en la vista
Edit
tienen el formatomodel.property
(por ejemplo,model.Title
). Las propiedades del modelo en la vistaEditVm
tienen el formatomodel.Album.property
(por ejemplo,model.Album.Title
). Esto se debe a que a la vistaEditVM
se le pasa un contenedor para un objetoAlbum
, no un objetoAlbum
como en la vistaEdit
. - El segundo parámetro de DropDownList procede del modelo de vista, no del objeto ViewBag.
- El asistente BeginForm en la vista
EditVM
devuelve explícitamente al método de acciónEdit
. Al devolver a la acciónEdit
, no es necesario escribir una acciónHTTP POST EditVM
y se puede reutilizar la acciónHTTP POST
Edit
.
Ejecute la aplicación y edite un álbum. Cambie la dirección URL para usar EditVM
. Cambie un campo y presione el botón Save para comprobar que el código funciona.
¿Qué enfoque debería usar?
Los tres enfoques mostrados son aceptables. Muchos desarrolladores prefieren pasar explícitamente SelectList
a DropDownList
mediante ViewBag
. Este enfoque tiene como ventaja adicional que ofrece la flexibilidad de usar un nombre más adecuado para la colección. La advertencia a tener en cuenta es que no se puede asignar al objeto ViewBag SelectList
el mismo nombre que la propiedad del modelo.
Algunos desarrolladores prefieren el enfoque de ViewModel. Otros consideran que el marcado más detallado y el HTML generado del enfoque ViewModel es una desventaja.
En esta sección, hemos aprendido tres enfoques para usar DropDownList con datos de categoría. En la siguiente sección, mostraremos cómo agregar una nueva categoría.