Parte 5: Editar formularios y plantillas
por Jon Galloway
MVC Music Store es una aplicación de tutorial que presenta y explica paso a paso cómo usar ASP.NET MVC y Visual Studio para el desarrollo web.
MVC Music Store es una implementación ligera de una tienda de ejemplo en la que se venden álbumes de música en línea e implementa la administración básica del sitio, el inicio de sesión de usuario y la funcionalidad del carro de la compra.
En esta serie de tutoriales se detallan todos los pasos realizados para compilar la aplicación de ejemplo ASP.NET MVC Music Store. En la parte 5 se describe cómo editar formularios y plantillas.
En el capítulo anterior, se cargaban datos desde la base de datos y se mostraban. En este capítulo, también se habilitará la edición de los datos.
Creación de StoreManagerController
Para empezar, creará un controlador llamado StoreManagerController. Para este controlador, se aprovecharán las características de scaffolding disponibles en la actualización de herramientas de ASP.NET MVC 3. Establezca las opciones del cuadro de diálogo Agregar controlador como se muestra a continuación.
Al hacer clic en el botón Agregar, verá que el mecanismo de scaffolding de ASP.NET MVC 3 realiza gran cantidad del trabajo automáticamente:
- Crea StoreManagerController con una variable de Entity Framework local
- Agrega una carpeta StoreManager a la carpeta Views del proyecto
- Agrega la vista Create.cshtml, Delete.cshtml, Details.cshtml, Edit.cshtml e Index.cshtml, fuertemente tipada a la clase Album
La nueva clase de controlador StoreManager incluye acciones de controlador CRUD (crear, leer, actualizar, eliminar) que saben cómo trabajar con la clase de modelo Album y usar el contexto de Entity Framework para el acceso a la base de datos.
Modificación de una vista con scaffolding
Es importante recordar que, mientras que este código se ha generado de forma automática, es código estándar de ASP.NET MVC, similar al que se ha escrito en este tutorial. Está pensado para ahorrarle el tiempo que dedicaría a escribir código de controlador reutilizable y crear manualmente las vistas fuertemente tipadas, pero no es el tipo de código generado que puede haber visto precedido de advertencias graves en los comentarios sobre cómo no lo debe cambiar. Es su código y se espera que lo cambie.
Por tanto, comenzará con una edición rápida de la vista StoreManager Index (/Views/StoreManager/Index.cshtml). En esta vista se mostrará una tabla que enumera los álbumes de la tienda con vínculos Editar/ Detalles /Eliminar e incluye las propiedades públicas de Album. Se quitará el campo AlbumArtUrl, ya que no es muy útil en esta pantalla. En la sección <table> del código de la vista, quite los elementos <th> y <td> que rodean a las referencias AlbumArtUrl, como se indica en las líneas resaltadas siguientes:
<table>
<tr>
<th>
Genre
</th>
<th>
Artist
</th>
<th>
Title
</th>
<th>
Price
</th>
<th>
AlbumArtUrl
</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.DisplayFor(modelItem => item.AlbumArtUrl)
</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>
El código de la vista modificado será similar al siguiente:
@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>
Un primer vistazo a Store Manager
Ahora ejecute la aplicación y vaya a /StoreManager/. Esto muestra el índice de Store Manager que se acaba de modificar, con una lista de los álbumes en la tienda y los vínculos Editar, Detalles y Eliminar.
Al hacer clic en el vínculo Editar se muestra un formulario de edición con campos para el álbum, incluidas las listas desplegables de Género y Artista.
Haga clic en el vínculo "Volver a la lista" en la parte inferior y, después, haga clic en el vínculo Detalles de un álbum. Esto muestra la información detallada de un álbum individual.
De nuevo, haga clic en el vínculo Volver a la lista y luego en un vínculo Eliminar. Esto muestra un cuadro de diálogo de confirmación, con los detalles del álbum y se le pregunta si realmente lo quiere eliminar.
Al hacer clic en el botón Eliminar de la parte inferior se eliminará el álbum y se le devolverá a la página Índice, en la que se muestra el álbum eliminado.
No ha terminado con Store Manager, pero tiene un controlador operativo y código de vista para las operaciones CRUD como punto de partida.
Examen del código del controlador Store Manager
El controlador Store Manager contiene una buena cantidad de código. Se analizará de arriba abajo. El controlador incluye algunos espacios de nombres estándar para un controlador MVC, así como una referencia al espacio de nombres Models. El controlador tiene una instancia privada de MusicStoreEntities, usada por cada una de las acciones del controlador para el acceso a datos.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class StoreManagerController : Controller
{
private MusicStoreEntities db = new MusicStoreEntities();
Acciones de índice y detalles de Store Manager
La vista de índice recupera una lista de álbumes, incluida la información de género y artista a la que se hace referencia en cada álbum, como ha visto antes al trabajar en el método Store Browse. La vista Índice sigue las referencias a los objetos vinculados para que pueda mostrar el nombre de género y el nombre del artista de cada álbum, por lo que el controlador es eficaz y consulta esta información en la solicitud original.
//
// GET: /StoreManager/
public ViewResult Index()
{
var albums = db.Albums.Include(a => a.Genre).Include(a => a.Artist);
return View(albums.ToList());
}
La acción controlador de detalles del controlador StoreManager funciona exactamente igual que la acción de detalles del controlador Store que ha escrito antes: consulta el álbum por identificador mediante el método Find() y, después, lo devuelve a la vista.
//
// GET: /StoreManager/Details/5
public ViewResult Details(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Métodos de acción Create
Los métodos de acción Create son un poco diferentes a los que ha visto hasta ahora, ya que controlan la entrada del formulario. Cuando un usuario visita por primera vez /StoreManager/Create/ se le mostrará un formulario vacío. Esta página HTML contendrá un elemento <form> con elementos de entrada de lista desplegable y cuadro de texto donde puede escribir los detalles del álbum.
Una vez que el usuario rellene los valores del formulario Album, puede presionar el botón "Guardar" para volver a enviar estos cambios a la aplicación para guardarlos en la base de datos. Cuando el usuario presiona el botón "Guardar", el control <form> volverá a realizar una solicitud HTTP-POST en la dirección URL /StoreManager/Create/ y enviará los valores de <form> como parte de HTTP-POST.
ASP.NET MVC permite dividir fácilmente la lógica de estos dos escenarios de invocación de URL al permitir implementar dos métodos de acción "Create" independientes dentro de la clase StoreManagerController: uno para controlar la navegación HTTP-GET inicial a la dirección URL /StoreManager/Create/ y el otro para controlar la acción HTTP-POST de los cambios enviados.
Envío de información a una vista mediante ViewBag
Ha usado ViewBag anteriormente en este tutorial, pero no se ha hablado mucho sobre este elemento. ViewBag permite pasar información a la vista sin usar un objeto de modelo fuertemente tipado. En este caso, la acción de controlador Edit HTTP-GET debe pasar una lista de géneros y artistas al formulario para rellenar las listas desplegables y la manera más sencilla de hacerlo es devolverlos como elementos ViewBag.
ViewBag es un objeto dinámico, lo que significa que puede escribir ViewBag.Foo o ViewBag.SuNombre sin necesidad de código para definir esas propiedades. En este caso, el código del controlador usa ViewBag.GenreId y ViewBag.ArtistId para que los valores desplegables enviados con el formulario sean GenreId y ArtistId, que son las propiedades de Album que establecerán.
Estos valores desplegables se devuelven al formulario mediante el objeto SelectList, que se crea solo para ese propósito. Esto se hace mediante código similar al siguiente:
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
Como puede ver en el código del método de acción, se usan tres parámetros para crear este objeto:
- La lista de elementos que se mostrarán en la lista desplegable. Tenga en cuenta que esto no es solo una cadena: se pasa una lista de géneros.
- El siguiente parámetro que se pasa a SelectList es el valor seleccionado. De este modo, SelectList sabe cómo seleccionar previamente un elemento de la lista. Esto será más fácil de entender cuando vea el formulario Edit, que es bastante similar.
- El parámetro final es la propiedad que se va a mostrar. En este caso, esto indica que la propiedad Genre.Name es lo que se mostrará al usuario.
Teniendo esto en cuenta, la acción HTTP-GET Create es bastante sencilla: dos elementos SelectList se agregan a ViewBag y no se pasa ningún objeto de modelo al formulario (ya que aún no se ha creado).
//
// GET: /StoreManager/Create
public ActionResult Create()
{
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name");
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name");
return View();
}
Asistentes HTML para mostrar las listas desplegables en la vista Create
Como se ha hablado sobre cómo se pasan los valores desplegables a la vista, ahora se examinará la vista para ver cómo se muestran esos valores. En el código de vista (/Views/StoreManager/Create.cshtml), verá que se realiza la siguiente llamada para mostrar la lista desplegable Genre.
@Html.DropDownList("GenreId",
String.Empty)
Esto se conoce como asistente HTML: un método de utilidad que realiza una tarea de vista común. Los asistentes HTML son muy útiles para que el código de vista sea conciso y legible. El asistente Html.DropDownList lo proporciona ASP.NET MVC, pero como verá más adelante, es posible crear asistentes propios para el código de vista que se volverá a usar en la aplicación.
Al la llamada Html.DropDownList solo hay que indicarle dos datos: dónde obtener la lista que se va a mostrar y qué valor (si existe) se debe seleccionar previamente. El primer parámetro, GenreId, indica a DropDownList que busque un valor denominado GenreId en el modelo o ViewBag. El segundo parámetro se usa para indicar el valor que se va a mostrar como seleccionado inicialmente en la lista desplegable. Como es un formulario Create, no hay ningún valor que se seleccione previamente, por lo que se pasa String.Empty.
Control de los valores del formulario publicado
Como se ha indicado antes, hay dos métodos de acción asociados a cada formulario. La primera controla la solicitud HTTP-GET y muestra el formulario. El segundo controla la solicitud HTTP-POST, que contiene los valores de formulario enviados. Observe que la acción del controlador tiene un atributo [HttpPost], que indica a ASP.NET MVC que solo debe responder a las solicitudes HTTP-POST.
//
// 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 acción tiene cuatro responsabilidades:
-
- Leer los valores del formulario
-
- Comprobar si los valores del formulario superan las reglas de validación
-
- Si el envío del formulario es válido, se guardan los datos y se muestra la lista actualizada
-
- Si el envío del formulario no es válido, se vuelve a mostrar el formulario con errores de validación
Lectura de valores de formulario con enlace de modelos
La acción del controlador procesa un envío de formulario que incluye valores para GenreId y ArtistId (en la lista desplegable), y valores de cuadro de texto para Title, Price y AlbumArtUrl. Aunque es posible acceder directamente a los valores de formulario, un mejor enfoque consiste en usar las funcionalidades de enlace de modelos integradas en ASP.NET MVC. Cuando una acción del controlador toma un tipo de modelo como parámetro, ASP.NET MVC intentará rellenar un objeto de ese tipo mediante entradas de formulario (así como valores de ruta y cadena de consulta). Para ello, busca valores cuyos nombres coinciden con las propiedades del objeto de modelo, por ejemplo, al establecer el nuevo valor GenreId del objeto Album, busca una entrada con el nombre GenreId. Al crear vistas con los métodos estándar en ASP.NET MVC, los formularios siempre se representarán con nombres de propiedad como nombres de campo de entrada, por lo que estos nombres de campo simplemente coincidirán.
Validación del modelo
El modelo se valida con una llamada simple a ModelState.IsValid. Aún no ha agregado ninguna regla de validación a la clase Album, lo hará más adelante, por lo que ahora esta comprobación no tiene mucho que hacer. Lo importante es que esta comprobación ModelStat.IsValid se adaptará a las reglas de validación incluidas en el modelo, por lo que los cambios futuros en las reglas de validación no requerirán ninguna actualización del código de acción del controlador.
Guardado de los valores enviados
Si el envío del formulario pasa la validación, es el momento de guardar los valores en la base de datos. Con Entity Framework, para esto solo es necesario agregar el modelo a la colección Albums y llamar a SaveChanges.
db.Albums.Add(album);
db.SaveChanges();
Entity Framework genera los comandos SQL adecuados para conservar el valor. Después de guardar los datos, se vuelve a la lista de álbumes para poder ver la actualización. Para ello, se devuelve RedirectToAction con el nombre de la acción del controlador que se quiere mostrar. En este caso, es el método Index.
Representación de envíos de formularios no válidos con errores de validación
En el caso de entradas de formulario no válidas, los valores desplegables se agregan a ViewBag (como en el caso HTTP-GET) y los valores de modelo enlazados se vuelven a pasar a la vista para su representación. Los errores de validación se muestran automáticamente mediante el asistente HTML @Html.ValidationMessageFor.
Prueba del formulario Create
Para probarlo, ejecute la aplicación y vaya a /StoreManager/Create/: se mostrará el formulario en blanco que ha devuelto el método Create HTTP-GET de StoreController.
Rellene algunos valores y haga clic en el botón Crear para enviar el formulario.
Control de ediciones
El par de acciones Edit (HTTP-GET y HTTP-POST) son muy similares a los métodos de acción Create que se acaban de examinar. Como el escenario de edición implica trabajar con un álbum existente, el método Edit HTTP-GET carga el álbum en función del parámetro "id", pasado mediante la ruta. Este código para recuperar un álbum por AlbumId es el mismo que ha visto antes en la acción del controlador de detalles. Como sucede con el método Create/HTTP-GET, los valores desplegables se devuelven por medio de ViewBag. Esto le permite devolver una instancia de Album como objeto de modelo a la vista (que está fuertemente tipada en la clase Album) mientras se pasan datos adicionales (por ejemplo, una lista de géneros) por medio de ViewBag.
//
// GET: /StoreManager/Edit/5
public ActionResult Edit(int id)
{
Album album = db.Albums.Find(id);
ViewBag.GenreId = new SelectList(db.Genres, "GenreId", "Name", album.GenreId);
ViewBag.ArtistId = new SelectList(db.Artists, "ArtistId", "Name", album.ArtistId);
return View(album);
}
La acción Edit HTTP-POST es muy similar a la acción Create HTTP-POST. La única diferencia es que en lugar de agregar un nuevo álbum a la colección db.Albums, se busca la instancia actual de Album mediante db.Entry(album) y su estado se establece en Modified. Esto indica a Entity Framework que se va a modificar un álbum existente en lugar de crear uno.
//
// POST: /StoreManager/Edit/5
[HttpPost]
public ActionResult Edit(Album album)
{
if (ModelState.IsValid)
{
db.Entry(album).State = EntityState.Modified;
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);
}
Para probarlo, ejecute la aplicación, vaya a /StoreManger/y haga clic en el vínculo Edit de un álbum.
Esto muestra el formulario Edit mostrado por el método Edit HTTP-GET. Rellene algunos valores y haga clic en el botón Guardar.
Esto publica el formulario, guarda los valores y le devuelve a la lista Album, en la que se muestra que los valores se ha actualizado.
Control de la eliminación
La eliminación sigue el mismo patrón que Edit y Create, con una acción de controlador para mostrar el formulario de confirmación y otra acción de controlador para controlar el envío del formulario.
La acción de controlador HTTP-GET Delete es exactamente la misma que la acción de controlador de detalles de Store Manager anterior.
//
// GET: /StoreManager/Delete/5
public ActionResult Delete(int id)
{
Album album = db.Albums.Find(id);
return View(album);
}
Se muestra un formulario fuertemente tipado en un tipo Album, mediante la plantilla de contenido de la vista Delete.
La plantilla Delete muestra todos los campos del modelo, pero se puede simplificar un poco. Cambie el código de vista en /Views/StoreManager/Delete.cshtml por lo siguiente.
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Delete";
}
<h2>Delete Confirmation</h2>
<p>Are you sure you want to delete the album titled
<strong>@Model.Title</strong>?
</p>
@using (Html.BeginForm()) {
<p>
<input type="submit" value="Delete" />
</p>
<p>
@Html.ActionLink("Back to
List", "Index")
</p>
}
Esto muestra una confirmación de eliminación simplificada.
Al hacer clic en el botón Eliminar, el formulario se volverá a publicar en el servidor, que ejecuta la acción DeleteConfirmed.
//
// POST: /StoreManager/Delete/5
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
Album album = db.Albums.Find(id);
db.Albums.Remove(album);
db.SaveChanges();
return RedirectToAction("Index");
}
La acción de controlador de HTTP-POST Delete realiza las siguientes acciones:
-
- Carga el álbum por id.
-
- Elimina el álbum y guarda los cambios
-
- Redirige al índice, en el que se muestra que el álbum se ha quitado de la lista
Para probarlo, ejecute la aplicación y vaya a /StoreManager. Seleccione un álbum de la lista y haga clic en el vínculo Delete.
Esto muestra la pantalla de confirmación Delete.
Al hacer clic en el botón Eliminar, se quita el álbum y se devuelve a la página de índice de Store Manager, en la que se muestra que se ha eliminado el álbum.
Uso de un asistente HTML personalizado para truncar texto
Hay un posible problema con la página de índice de Store Manager. Las propiedades Album Title y Artist Name pueden ser lo suficientemente largas como para afectar al formato de la tabla. Se creará un asistente HTML personalizado para poder truncar fácilmente estas y otras propiedades en las vistas.
La sintaxis @helper de Razor ha hecho que sea bastante fácil crear funciones auxiliares propias para su uso en las vistas. Abra la vista /Views/StoreManager/Index.cshtml y agregue el código siguiente directamente después de la línea @model.
@helper Truncate(string
input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
Este método auxiliar toma una cadena y una longitud máxima permitida. Si el texto proporcionado es menor que la longitud especificada, el asistente lo genera tal y como está. Si es más largo, trunca el texto y representa "..." para el resto.
Ahora puede usar el asistente Truncate para asegurarse de que las propiedades Album Title y Artist Name tienen menos de 25 caracteres. El código de vista completo con el nuevo asistente Truncate se muestra a continuación.
@model IEnumerable<MvcMusicStore.Models.Album>
@helper Truncate(string input, int length)
{
if (input.Length <= length) {
@input
} else {
@input.Substring(0, length)<text>...</text>
}
}
@{
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>
@Truncate(item.Artist.Name, 25)
</td>
<td>
@Truncate(item.Title, 25)
</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>
Ahora, al navegar a la dirección URL /StoreManager/, los álbumes y títulos se mantienen por debajo de las longitudes máximas.
Nota: Esto muestra el caso sencillo de crear y usar un asistente en una vista. Para más información sobre la creación de asistentes que puede usar en todo el sitio, vea esta entrada de blog: http://bit.ly/mvc3-helper-options