Share via


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.

Image of Solution Explorer Add Controller dialog box

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:

Image of Album schema

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.

Image of some data from the Albums table

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.

Image of Disco selected genre

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

)
  1. Items: un objeto IEnumerable que contiene los elementos de la lista. En el ejemplo anterior, la lista de géneros devueltos por db.Genres.
  2. dataValueField: el nombre de la propiedad en la lista de IEnumerable que contiene el valor de clave. En el ejemplo anterior, GenreId y ArtistId.
  3. 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 mostrado anteriormente, no se pasa ningún modelo, por lo que la vista obtiene un modelo Albumnull. 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.

Image of the default first element

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.

Image showing the creation of a new album

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.

Image of the Fiddler tool.

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.

Image of Jazz album selection to 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;.

Image selecting resolve

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.

Image showing Add View dialog

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 formato model.property (por ejemplo, model.Title). Las propiedades del modelo en la vista EditVm tienen el formato model.Album.property (por ejemplo, model.Album.Title). Esto se debe a que a la vista EditVM se le pasa un contenedor para un objeto Album, no un objeto Album como en la vista Edit.
  • 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ón Edit. Al devolver a la acción Edit, no es necesario escribir una acción HTTP POST EditVM y se puede reutilizar la acción HTTP POSTEdit.

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.

Image with U R L change to Edit V M

¿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.