Parte 3: Vistas y ViewModels

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 la tienda de muestras que vende á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 de 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. La parte 3 describe las Vistas y ViewModels.

Hasta ahora, solo hemos devuelto cadenas de acciones de controlador. Es una buena manera de hacerse una idea de cómo funcionan los controladores, pero no es la forma adecuada de crear una aplicación web real. Vamos a necesitar una mejor manera de generar HTML de vuelta a los exploradores que visitan nuestro sitio: una en la que podemos usar archivos de plantilla para personalizar más fácilmente el envío de contenido HTML. Eso es exactamente lo que hacen las vistas.

Adición de una plantilla de vista

Para usar una plantilla de vista, cambiaremos el método HomeController Index para que devuelva un ActionResult y genere View(), como se indica a continuación:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

El cambio anterior indica que, en lugar de devolver una cadena, queremos usar una "Vista" para generar un resultado.

Ahora agregaremos una plantilla de vista adecuada a nuestro proyecto. Para ello, coloque el cursor de texto en el método de acción Index y, a continuación, haga clic con el botón derecho y seleccione "Agregar vista". Se abrirá el cuadro de diálogo Agregar vista:

Screenshot of the menu that shows the add view selection.Screenshot of the add view dialog box, with menu options for selecting and adding your view.

El cuadro de diálogo "Agregar vista" nos permite generar archivos de plantilla de vista de forma rápida y fácil. De forma predeterminada, el cuadro de diálogo "Agregar vista" se rellena previamente con el nombre de la plantilla de vista que se va a crear para que coincida con el método de acción que la usará. Dado que usamos el menú contextual "Agregar vista" dentro del método de acción Index() de nuestro HomeController, el cuadro de diálogo "Agregar vista" anterior tiene "Index" como nombre de vista rellenado previamente de forma predeterminada. No es necesario cambiar ninguna de las opciones de este cuadro de diálogo, por lo que haga clic en el botón Agregar.

Al hacer clic en el botón Agregar, Visual Web Developer creará una nueva plantilla de vista Index.cshtml para nosotros en el directorio \Views\Home, creando la carpeta si aún no existe.

Screenshot of the Solution Explorer drop-down menu, showing the different files in the M V C Music Store.

La ubicación de nombre y carpeta del archivo "Index.cshtml" es importante y sigue las convenciones de nomenclatura predeterminadas de MVC ASP.NET. El nombre del directorio, \Views\Home, coincide con el controlador, que se denomina HomeController. El nombre de la plantilla de vista, Index, coincide con el método de acción del controlador que mostrará la vista.

ASP.NET MVC nos permite evitar tener que especificar explícitamente el nombre o la ubicación de una plantilla de vista cuando se usa esta convención de nomenclatura para devolver una vista. De forma predeterminada, representará la plantilla de vista \Views\Home\Index.cshtml cuando escribamos código como el siguiente en nuestro HomeController:

public class HomeController : Controller
{
    //
    // GET: /Home/
    public ActionResult Index()
    {
        return View();
    }
}

Visual Web Developer creó y abrió la plantilla de vista "Index.cshtml" después de hacer clic en el botón "Agregar" dentro del cuadro de diálogo "Agregar vista". A continuación se muestra el contenido de Index.cshtml.

@{
    ViewBag.Title = "Index";
}
<h2>Index</h2>

Esta vista usa la sintaxis Razor, que es más concisa que el motor de vistas de Web Forms usado en ASP.NET Web Forms y versiones anteriores de ASP.NET MVC. El motor de vistas Web Forms todavía está disponible en ASP.NET MVC 3, pero muchos desarrolladores encuentran que el motor de vistas Razor funciona realmente bien en el desarrollo de ASP.NET MVC.

Las tres primeras líneas establecen el título de la página con ViewBag.Title. Veremos cómo funciona con más detalle pronto, pero primero vamos a actualizar el texto del encabezado de texto y visualizar la página. Actualice la etiqueta <h2> para que diga "This is the Home Page" (Esta es la página principal), como se muestra a continuación.

@{
    ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>

La ejecución de la aplicación muestra que el nuevo texto está visible en la página principal.

Screenshot of the music store's browser's home page, showing the text 'This is the Home Page' under the logo image.

Uso de un diseño para elementos de sitio comunes

La mayoría de los sitios web tienen contenido que se comparte entre muchas páginas: navegación, pies de página, imágenes de logotipo, referencias de hoja de estilos, etc. El motor de vistas Razor facilita la administración mediante una página denominada _Layout.cshtml que se ha creado automáticamente para nosotros dentro de la carpeta /Views/Shared.

Screenshot of the Music Store drop-down file menu, showing the file path to the shared folder located inside the view folder.

Haga doble clic en esta carpeta para ver el contenido, que se muestra a continuación.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"     
            type="text/javascript"></script> 
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")"
            type="text/javascript"></script>
</head>
<body>
    @RenderBody()
</body>
</html>

El comando @RenderBody() mostrará el contenido de nuestras vistas individuales y cualquier contenido común que queramos que aparezca fuera de eso se puede agregar al marcado _Layout.cshtml. Queremos que MVC Music Store tenga un encabezado común con vínculos a nuestra página principal y el área Store (Tienda) en todas las páginas del sitio, por lo que se agregará a la plantilla directamente encima de la instrucción @RenderBody().

<!DOCTYPE html>
<html>
<head>
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
    <div id="header">
        <h1>
            ASP.NET MVC MUSIC STORE</h1>
        <ul id="navlist">
            <li class="first"><a href="/"
id="current">Home</a></li>
            <li><a
href="/Store/">Store</a></li>
        </ul>
    </div>
    @RenderBody()
</body>
</html>

Actualización de la hoja de estilos

La plantilla de proyecto vacía incluye un archivo CSS muy simplificado que solo incluye estilos usados para mostrar mensajes de validación. Nuestro diseñador ha proporcionado algunas imágenes y CSS adicionales para definir la apariencia de nuestro sitio, por lo que los agregaremos ahora.

El archivo CSS actualizado y las imágenes se incluyen en el directorio Content de MvcMusicStore-Assets.zip, que está disponible en MVC-Music-Store. Seleccionaremos ambos en el Explorador de Windows y los colocaremos en la carpeta Content de la solución en Visual Web Developer, como se muestra a continuación:

Side by side screenshot of the content directory and Music Store drop-down menu, showing the file path to the images folder in the content folder.

Se le pedirá que confirme si desea sobrescribir el archivo Site.css existente. Haga clic en Sí.

Screenshot of the warning pop-up box that appears, requesting to confirm the overwrite action by asking if you want to replace the existing file.

La carpeta Content de la aplicación aparecerá ahora de la siguiente manera:

Screenshot of the music store, drop-down menu, highlighting the content folder, showing the new image folder with the list of images underneath.

Ahora vamos a ejecutar la aplicación y ver cómo se ven los cambios en la página principal.

Screenshot of the music store browser window home page, with the image that was selected, along with the words 'this is the home page' underneath.

  • Revisemos lo que ha cambiado: el método de acción Index de HomeController encontró y mostró la plantilla \Views\Home\Index.cshtmlView, a pesar del código "return View()", porque nuestra plantilla de vista siguió la convención de nomenclatura estándar.
  • La página principal muestra un mensaje de bienvenida sencillo que se define dentro de la plantilla de vista \Views\Home\Index.cshtml.
  • La página principal usa nuestra plantilla _Layout.cshtml, por lo que el mensaje de bienvenida está incluido en el diseño HTML del sitio estándar.

Uso de un modelo para pasar información a nuestra vista

Una plantilla de vista que solo muestra HTML codificado de forma rígida no va a hacer un sitio web muy interesante. Para crear un sitio web dinámico, en su lugar queremos pasar información de nuestras acciones de controlador a nuestras plantillas de vista.

En el patrón Model-View-Controller, el término Model hace referencia a objetos que representan los datos de la aplicación. A menudo, los objetos de modelo corresponden a tablas de la base de datos, pero esto no es estrictamente cierto.

Los métodos de acción de controlador que devuelven ActionResult pueden pasar un objeto de modelo a la vista. Esto permite a un controlador empaquetar limpiamente toda la información necesaria para generar una respuesta y, a continuación, pasar esta información a una plantilla de vista para usarla para generar la respuesta HTML adecuada. Esto es más fácil de entender al verlo en acción, así que vamos a empezar.

En primer lugar, crearemos algunas clases Model para representar géneros y álbumes dentro de nuestra tienda. Comencemos por crear la clase Genre (Género). Haga clic con el botón derecho en la carpeta "Models" del proyecto, elija la opción "Agregar clase" y asigne al archivo el nombre "Genre.cs".

Screenshot of three side-by-side menu boxes, showing the file path directions, from right to left, to the class selection

Screenshot of the add new item menu options, displaying three menus selecting a template, sort style, and type; then the name field bar at the bottom.

A continuación, agregue una propiedad "public string Name" a la clase que se creó:

public class Genre
{
    public string Name { get; set; }
}

Nota: por si se lo pregunta, la notación { get; set; } está usando la característica de propiedades implementadas automáticamente de C#. Esto nos da las ventajas de una propiedad sin necesidad de declarar un campo de respaldo.

A continuación, siga los mismos pasos para crear una clase Album (denominada Album.cs) que tenga una propiedad Title (Título) y Genre (Género):

public class Album
{
    public string Title { get; set; }
    public Genre Genre { get; set; }
}

Ahora podemos modificar StoreController para que use vistas que muestran información dinámica de nuestro modelo. Si, con fines de demostración, llamamos a nuestros álbumes en función del identificador de solicitud, podríamos mostrar esa información como en la vista siguiente.

Screenshot of the home page on the browser, with the image logo, the current album name and clickable home and store buttons on the top right corner.

Comenzaremos cambiando la acción Store Details (Información de la tienda) para que muestre la información de un solo álbum. Agregue una instrucción "using" a la parte superior de la clase StoreControllers para incluir el espacio de nombres MvcMusicStore.Models, por lo que no es necesario escribir MvcMusicStore.Models.Album cada vez que queremos usar la clase de álbum. La sección "usings" de esa clase debería aparecer ahora como se indica a continuación.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;

A continuación, actualizaremos la acción de controlador Details para que devuelva un ActionResult en lugar de una cadena, como hicimos con el método Index de HomeController.

public ActionResult Details(int id)

Ahora podemos modificar la lógica para que devuelva un objeto Album a la vista. Más adelante en este tutorial se recuperarán los datos de una base de datos, pero por ahora usaremos "datos ficticios" para empezar.

public ActionResult Details(int id)
 {
    var album = new Album { Title = "Album " + id };
    return View(album);
 }

Nota: si no está familiarizado con C#, puede suponer que el uso de var significa que nuestra variable de álbum está enlazada en tiempo de ejecución. Eso no es correcto: el compilador de C# usa la inferencia de tipos en función de lo que estamos asignando a la variable para determinar que el álbum es de tipo Album y compilar la variable de álbum local como un tipo Album, por lo que obtenemos la comprobación en tiempo de compilación y la compatibilidad con el editor de código de Visual Studio.

Ahora vamos a crear una plantilla de vista que use nuestro Album para generar una respuesta HTML. Antes de hacerlo, es necesario compilar el proyecto para que el cuadro de diálogo Agregar vista conozca nuestra clase Album recién creada. Para compilar el proyecto, seleccione el elemento de menú Depurar⇨Compilar MvcMusicStore (adicionalmente, puede usar el acceso directo Ctrl+Mayús+B para compilar el proyecto).

Screenshot of the music store document editor, with the 'build' tab selected in the drop-down menu, highlighting the 'build M V C music store option.

Ahora que hemos configurado nuestras clases auxiliares, estamos listos para crear la plantilla de vista. Haga clic con el botón derecho en el método Details y seleccione "Agregar vista..." en el menú contextual.

Screenshot of the view template menu, displaying over a code snippet, and highlighting the 'add view' option.

Vamos a crear una nueva plantilla Vista como hicimos antes con HomeController. Dado que la estamos creando desde StoreController, se generará de forma predeterminada en un archivo \Views\Store\Index.cshtml.

A diferencia de antes, vamos a activar la casilla "Crear una vista fuertemente tipada". A continuación, vamos a seleccionar nuestra clase "Album" en la lista desplegable "Ver clase de datos". Esto hará que el cuadro de diálogo "Agregar vista" cree una plantilla Vista que espera que se le pase un objeto Album.

Screenshot of the add view menu window, showing the 'create strongly-typed view' clickable checkbox and the album's model class.

Al hacer clic en el botón "Agregar" se creará la plantilla Vista \Views\Store\Details.cshtml, que contiene el código siguiente.

@model MvcMusicStore.Models.Album
@{
    ViewBag.Title = "Details";
}
<h2>Details</h2>

Observe la primera línea, que indica que esta vista está fuertemente tipada en nuestra clase Album. El motor de vistas Razor entiende que se ha pasado un objeto Album, por lo que podemos acceder fácilmente a las propiedades del modelo e incluso beneficiarnos de IntelliSense en el editor de Visual Web Developer.

Actualice la etiqueta <h2> para que muestre la propiedad Album's Title (Título del álbum) modificando esa línea para que aparezca de la siguiente manera.

<h2>Album: @Model.Title</h2>

Observe que IntelliSense se desencadena al escribir el punto después de la palabra clave @Model, mostrando las propiedades y los métodos que admite la clase Album.

Ahora volveremos a ejecutar nuestro proyecto y visitaremos la dirección URL /Store/Details/5. Veremos detalles de un álbum como el siguiente.

Screenshot of the home page browser window, with the image logo on the top left, and album name under it.

Ahora realizaremos una actualización similar al método de acción Store Browse (Navegación por la tienda). Actualice el método para que devuelva ActionResult y modifique la lógica del método para que cree un nuevo objeto Genre y lo devuelva a la vista.

public ActionResult Browse(string genre)
 {
    var genreModel = new Genre { Name = genre };
    return View(genreModel);
 }

Haga clic con el botón derecho en el método Browse y seleccione "Agregar vista..." en el menú contextual. A continuación, agregue una vista fuertemente tipada a la clase Genre.

Screenshot of the context menu, which shows the 'create a strongly-typed-view' being selected and the current model class boxed in red.

Actualice el elemento <h2> en el código de vista (en /Views/Store/Browse.cshtml) para mostrar la información de Genre (género).

@model MvcMusicStore.Models.Genre
@{
    ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>

Ahora, vuelva a ejecutar el proyecto y vaya a /Store/Browse?Genre=Disco URL. Veremos la página Browse como se muestra a continuación.

Screenshot of the browser's home page window, displaying the 'browsing genre: disco' message under the logo image.

Por último, vamos a realizar una actualización ligeramente más compleja al método de acción Store Index y la vista para mostrar una lista de todos los géneros de nuestra tienda. Lo haremos usando una lista de géneros como nuestro objeto de modelo, en lugar de un solo género.

public ActionResult Index()
{
    var genres = new List<Genre>
    {
        new Genre { Name = "Disco"},
        new Genre { Name = "Jazz"},
        new Genre { Name = "Rock"}
    };
    return View(genres);
 }

Haga clic con el botón derecho en el método de acción Store Index y seleccione Agregar vista como antes, seleccione Genre como clase modelo y presione el botón Agregar.

Screenshot of the 'add view' window menu, showing the model class selection within a red box, then the add button below.

En primer lugar, cambiaremos la declaración @model para indicar que la vista esperará varios objetos Genre en lugar de uno solo. Cambie la primera línea de /Store/Index.cshtml para que sea como se indica a continuación:

@model IEnumerable<MvcMusicStore.Models.Genre>

Esto indica al motor de vistas Razor que va a trabajar con un objeto de modelo que puede contener varios objetos de género. Estamos usando un IEnumerable<Genre> en lugar de un List<Genre>, ya que es más genérico, lo que nos permite cambiar el tipo de modelo más adelante a cualquier tipo de objeto que admita la interfaz IEnumerable.

A continuación, recorreremos los objetos Genre del modelo, como se muestra en el código de vista completado a continuación.

@model IEnumerable<MvcMusicStore.Models.Genre>
@{
    ViewBag.Title = "Store";
}
<h3>Browse Genres</h3>
<p>
    Select from @Model.Count()
genres:</p>
<ul>
    @foreach (var genre in Model)
    {
        <li>@genre.Name</li>
    }
</ul>

Observe que dispone de compatibilidad completa con IntelliSense a medida que especifica este código, de modo que cuando escriba "@Model" verá todos los métodos y propiedades compatibles con un IEnumerable de tipo Genre.

Screenshot of H T M L code snippet, with a menu bar over it, selecting the 'count <>' command.

Dentro del bucle "foreach", Visual Web Developer sabe que cada elemento es de tipo Genre, por lo que puede ver IntelliSense para cada tipo Genre.

Screenshot of the 'foreach loop' code, with a drop-down menu window and the 'name' option selected with 'string Genre dot name' popping up next to it.

A continuación, la característica scaffolding examinó el objeto Genre y determinó que cada uno tendrá una propiedad Name, por lo que los recorrerá y escribirá. También genera los vínculos Editar, Detalles y Eliminar para cada elemento individual. Aprovecharemos eso más adelante en nuestro administrador de la tienda, pero por ahora optaremos por una lista sencilla.

Cuando ejecutamos la aplicación y navegamos a /Store, vemos que se muestra tanto el recuento como la lista de géneros.

Screenshot of the browser window, showing the 'browse genre' title, then a message, asking for a genre selection, followed by the titles below it.

Nuestra dirección URL /Store que enumera los géneros actualmente enumera los nombres de género simplemente como texto sin formato. Vamos a cambiar esto para que, en lugar de texto sin formato, tengamos el vínculo de nombres de género a la dirección URL /Store/Browse adecuada, para que al hacer clic en un género musical como "Disco" vaya a la dirección URL /Store/Browse?genre=Disco. Podríamos actualizar nuestra plantilla de vista \Views\Store\Index.cshtml para generar estos vínculos mediante código como el siguiente (no escriba esto ya que vamos a mejorarlo):

<ul>
    @foreach (var genre in Model)
    {
        <li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li>
    }
</ul>

Esto funciona, pero podría provocar problemas más adelante, ya que se basa en una cadena codificada de forma rígida. Por ejemplo, si quisiéramos cambiar el nombre del controlador, tendríamos que buscar en el código los vínculos que deben actualizarse.

Un enfoque alternativo que se puede usar es aprovechar un método auxiliar HTML. ASP.NET MVC incluye métodos auxiliares HTML que están disponibles en nuestro código de plantilla de vista para realizar una variedad de tareas comunes como esta. El método auxiliar Html.ActionLink() es especialmente útil ya que facilita la compilación de vínculos HTML <a> y se ocupa de detalles molestos, como asegurarse de que las rutas de acceso de dirección URL están codificadas correctamente.

Html.ActionLink() tiene varias sobrecargas diferentes para permitir especificar tanta información como necesite para los vínculos. En el caso más sencillo, solo se proporciona el texto del vínculo y el método Action al que dirigirse cuando se haga clic en el hipervínculo en el cliente. Por ejemplo, podemos crear un vínculo al método "/Store/" Index() en la página Store Details con el texto de vínculo "Go to the Store Index" (Ir al índice de la tienda) mediante la siguiente llamada:

@Html.ActionLink("Go
to the Store Index", "Index")

Nota: en este caso, no es necesario especificar el nombre del controlador porque simplemente estamos vinculando a otra acción dentro del mismo controlador que está representando la vista actual.

Sin embargo, nuestros vínculos a la página Browse tendrán que pasar un parámetro, por lo que usaremos otra sobrecarga del método Html.ActionLink que toma tres parámetros:

    1. Texto del vínculo, que mostrará el nombre del género
    1. Nombre de acción del controlador ("Browse", Examinar)
    1. Valores de parámetros de ruta, especificando el nombre ("Genre", Género) y el valor ("Genre name", Nombre de género)

Para ello, aquí se muestra cómo escribiremos esos vínculos en la vista Store Index:

<ul>
    @foreach (var genre in Model)
    {
        <li>@Html.ActionLink(genre.Name,
"Browse", new { genre = genre.Name })</li>
    }
</ul>

Ahora, cuando volvamos a ejecutar nuestro proyecto y accedamos a la dirección URL de /Store/, veremos una lista de géneros. Cada género es un hipervínculo: cuando se hace clic en él, nos llevará a nuestra dirección URL /Store/Browse?genre=[genre].

Screenshot of the browser window, showing the Browse Genre title, with the message'select from 3 genres' followed by three bulleted genre selections.

El código HTML de la lista de géneros tiene este aspecto:

<ul>
    <li><a href="/Store/Browse?genre=Disco">Disco</a>
</li>
    <li><a href="/Store/Browse?genre=Jazz">Jazz</a>
</li>
    <li><a href="/Store/Browse?genre=Rock">Rock</a>
</li>
</ul>