Compartir a través de


Usar controladores y vistas para implementar una interfaz de usuario de lista/detalles

por Microsoft

Descargar PDF

Este es el paso 4 de un tutorial de la aplicación "NerdDinner" gratuito que le guía durante el proceso de compilación de una aplicación web pequeña, pero completa, con ASP.NET MVC 1.

En el paso 4 se muestra cómo agregar un controlador a la aplicación que aprovecha nuestro modelo para proporcionar a los usuarios una experiencia de navegación de lista y detalles de datos para las comidas en nuestro sitio web NerdDinner.

Si utiliza ASP.NET MVC 3, le recomendamos que siga los tutoriales Introducción a MVC 3 o Tienda de música de MVC.

NerdDinner Paso 4: Controladores y vistas

Con marcos web tradicionales (ASP clásico, PHP, ASP.NET Web Forms, etc.), las direcciones URL entrantes normalmente se asignan a archivos en disco. Por ejemplo: una solicitud de una dirección URL como "/Products.aspx" o "/Products.php" podría procesarse mediante un archivo "Products.aspx" o "Products.php".

Los marcos de MVC basados en web asignan direcciones URL al código de servidor de una manera ligeramente diferente. En lugar de asignar direcciones URL entrantes a los archivos, asignan direcciones URL a los métodos en las clases. Estas clases se denominan "Controladores" y son las responsables de procesar las solicitudes HTTP entrantes, controlar la entrada del usuario, recuperar y guardar datos, y determinar la respuesta que se devuelve al cliente (mostrar HTML, descargar un archivo, redirigir a una dirección URL diferente, etc.).

Ahora que hemos creado un modelo básico para nuestra aplicación NerdDinner, nuestro siguiente paso será agregar un controlador a la aplicación que lo aproveche para proporcionar a los usuarios una experiencia de navegación por las listas y los detalles de los datos para las comidas en nuestro sitio.

Incorporación de un controlador DinnersController

Comenzaremos haciendo clic con el botón derecho en la carpeta "Controllers" de nuestro proyecto web y seleccionaremos el comando de menú Agregar->controlador (también puede ejecutar este comando escribiendo Ctrl-M, Ctrl-C):

Screenshot of the Solution Explorer window showing the Controllers folder and the Add and Controller menu items highlighted in blue.

Se abrirá el cuadro de diálogo "Agregar controlador":

Screenshot of the Add Controller dialog showing the Controller Name field filled with the text Dinners Controller.

Asignaremos el nombre "DinnersController" al nuevo controlador y haremos clic en el botón "Agregar". Visual Studio agregará un archivo DinnersController.cs en nuestro directorio \Controllers:

Screenshot of the Solution Explorer window showing the Dinner Controllers dot c s file highlighted in blue.

También abrirá la nueva clase DinnersController en el editor de código.

Incorporación de los métodos de acción Index() y Details() a la clase DinnersController

Queremos permitir que los visitantes usen nuestra aplicación para examinar una lista de las próximas comidas y permitirles hacer clic en cualquier comida de la lista para ver detalles específicos sobre ella. Para ello, publicaremos las siguientes direcciones URL desde nuestra aplicación:

URL Propósito
/Dinners/ Visualización de una lista HTML de las próximas comidas
/Dinners/Details/[id] Muestra los detalles de una comida específica que se indica con el parámetro "id" insertado dentro de la dirección URL, que coincidirá con el DinnerID de la comida en la base de datos. Por ejemplo: /Dinners/Details/2 mostraría una página HTML con detalles de la comida cuyo valor DinnerID es 2.

Publicaremos implementaciones iniciales de estas direcciones URL agregando dos "métodos de acción" públicos a nuestra clase DinnersController como se indica a continuación:

public class DinnersController : Controller {

    //
    // HTTP-GET: /Dinners/

    public void Index() {
        Response.Write("<h1>Coming Soon: Dinners</h1>");
    }

    //
    // HTTP-GET: /Dinners/Details/2

    public void Details(int id) {
        Response.Write("<h1>Details DinnerID: " + id + "</h1>");
    }
}

A continuación, ejecutaremos la aplicación NerdDinner y usaremos nuestro explorador para invocarlos. Al escribir la dirección URL "/Dinners/" se ejecutará el método Index() y se devolverá la siguiente respuesta:

Screenshot of the response window generated from running the NerdDinner application, showing the text Coming Soon: Dinners.

Al escribir la dirección URL "/Dinners/Details/2" se ejecutará el método Details() y se devolverá la siguiente respuesta:

Screenshot of the response window generated from running the NerdDinner application, showing the text Details Dinner I D: 2.

Puede que se pregunte cómo sabe ASP.NET MVC crear nuestra clase DinnersController e invocar esos métodos. Para comprenderlo, echemos un vistazo rápido a cómo funciona el enrutamiento.

Descripción del enrutamiento de ASP.NET MVC

ASP.NET MVC incluye un potente motor de enrutamiento de direcciones URL que proporciona mucha flexibilidad para controlar la asignación de las direcciones URL a las clases de controlador. Nos permite personalizar completamente cómo ASP.NET MVC elige qué clase de controlador crear, qué método invocar en él, así como configurar diferentes formas de analizar las variables automáticamente desde la dirección URL/Querystring y pasarlas al método como argumentos de parámetro. Ofrece la flexibilidad de optimizar totalmente un sitio para SEO (optimización del motor de búsqueda), así como de publicar cualquier estructura de direcciones URL que deseemos desde una aplicación.

De forma predeterminada, los nuevos proyectos de ASP.NET MVC incluyen un conjunto preconfigurado de reglas de enrutamiento de direcciones URL ya registradas. Esto nos permite empezar a trabajar fácilmente en una aplicación sin tener que configurar explícitamente nada. Los registros de reglas de enrutamiento predeterminados se encuentran en la clase "Application" de nuestros proyectos, que podemos abrir haciendo doble clic en el archivo "Global.asax" en la raíz de nuestro proyecto:

Screenshot of the Solution Explorer window showing the Global dot a s a x file highlighted in blue and circled in red.

Las reglas de enrutamiento predeterminadas de ASP.NET MVC se registran en el método "RegisterRoutes" de esta clase:

public void RegisterRoutes(RouteCollection routes) {

    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        "Default",                                       // Route name
        "{controller}/{action}/{id}",                    // URL w/ params
        new { controller="Home", action="Index",id="" }  // Param defaults
    );
}

La llamada de método "routes.MapRoute()" anterior registra una regla de enrutamiento predeterminada que asigna las direcciones URL entrantes a las clases de controlador mediante el formato de dirección URL: "/{controller}/{action}/{id}", donde "controller" es el nombre de la clase de controlador para crear instancias, "action" es el nombre de un método público que se va a invocar en él e "id" es un parámetro opcional incrustado dentro de la dirección URL que se puede pasar como argumento al método. El tercer parámetro pasado a la llamada de método "MapRoute()" es un conjunto de valores predeterminados que se usarán para los valores de controlador, acción e identificador en caso de que no estén presentes en la dirección URL (Controller = "Home", Action = "Index", Id = "").

En la tabla a continuación se muestra cómo se asigna una variedad de direcciones URL mediante la regla de enrutamiento predeterminada "/{controllers}/{action}/{id}":

URL Clase de controlador Método de acción Parámetros pasados
/Dinners/Details/2 DinnersController Details(id) id=2
/Dinners/Edit/5 DinnersController Edit(id) id=5
/Dinners/Create DinnersController Create() N/D
/Dinners DinnersController Index() N/D
/Home HomeController Index() N/D
/ HomeController Index() N/D

Las tres últimas filas muestran los valores predeterminados (Controller = Home, Action = Index, Id = "") que se usan. Dado que el método "Index" se registra como nombre de acción predeterminado si no se especifica uno, las direcciones URL "/Dinners" y "/Home" hacen que se invoque el método de acción Index() en sus clases Controller. Dado que el controlador "Inicio" se registra como controlador predeterminado si no se especifica uno, la dirección URL "/" hace que se cree HomeController y se invoque el método de acción Index().

Si no le gustan estas reglas de enrutamiento de direcciones URL predeterminadas, la buena noticia es que son fáciles de cambiar: solo tiene que editarlas en el método RegisterRoutes anterior. Sin embargo, para nuestra aplicación NerdDinner, no vamos a cambiar ninguna de las reglas de enrutamiento de direcciones URL predeterminadas; en su lugar, las usaremos tal cual.

Uso del DinnerRepository de nuestro DinnersController

Ahora vamos a reemplazar nuestra implementación actual de los métodos de acción Index() y Details() de DinnersController por implementaciones que usen nuestro modelo.

Usaremos la clase DinnerRepository que creamos antes para implementar el comportamiento. Comenzaremos agregando una instrucción "using" que haga referencia al espacio de nombres "NerdDinner.Models" y, a continuación, declararemos una instancia del DinnerRepository como campo en nuestra clase DinnerController.

Más adelante en este capítulo, presentaremos el concepto de "inserción de dependencias" y mostraremos otra manera para que nuestros controladores obtengan una referencia a un DinnerRepository que permita mejores pruebas unitarias, pero por ahora solo crearemos una instancia de nuestro DinnerRepository insertada como se muestra a continuación.

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

namespace NerdDinner.Controllers {

    public class DinnersController : Controller {

        DinnerRepository dinnerRepository = new DinnerRepository();

        //
        // GET: /Dinners/

        public void Index() {
            var dinners = dinnerRepository.FindUpcomingDinners().ToList();
        }

        //
        // GET: /Dinners/Details/2

        public void Details(int id) {
            Dinner dinner = dinnerRepository.GetDinner(id);
        }
    }
}

Ahora estamos listos para generar una respuesta HTML mediante nuestros objetos de modelo de datos recuperados.

Uso de vistas con nuestro controlador

Aunque es posible escribir código dentro de nuestros métodos de acción para ensamblar HTML y, a continuación, usar el método auxiliar Response.Write() para devolverlo al cliente, el manejo de ese enfoque se vuelve bastante difícil rápidamente. Un enfoque mucho mejor es que solo realicemos la aplicación y la lógica de datos dentro de nuestros métodos de acción DinnersController y, a continuación, pasemos los datos necesarios para representar una respuesta HTML a una plantilla "de vista" independiente responsable de generar la representación HTML de ella. Como veremos en un momento, una plantilla "de vista" es un archivo de texto que normalmente contiene una combinación de marcado HTML y código de representación incrustado.

La separación de la lógica del controlador de la representación de la vista aporta varias ventajas importantes. En concreto, ayuda a aplicar una clara "separación de preocupaciones" entre el código de la aplicación y el formato o el código de representación de la interfaz de usuario. Esto facilita mucho la lógica de aplicación de la prueba unitaria independientemente de la lógica de representación de la interfaz de usuario. Facilita la modificación posterior de las plantillas de representación de la interfaz de usuario sin tener que realizar cambios en el código de la aplicación. Y puede agilizar la colaboración entre los desarrolladores y diseñadores en los proyectos.

Podemos actualizar nuestra clase DinnersController para indicar que queremos usar una plantilla de vista para devolver una respuesta de interfaz de usuario HTML cambiando las firmas de método de nuestros dos métodos de acción, si tenían un tipo de valor devuelto "void", que tengan "ActionResult" en su lugar. A continuación, podemos llamar al método auxiliar View() en la clase base Controller para devolver un objeto "ViewResult" como se indica a continuación:

public class DinnersController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // GET: /Dinners/

    public ActionResult Index() {

        var dinners = dinnerRepository.FindUpcomingDinners().ToList();

        return View("Index", dinners);
    }

    //
    // GET: /Dinners/Details/2

    public ActionResult Details(int id) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
            return View("NotFound");
        else
            return View("Details", dinner);
    }
}

La firma del método auxiliar View() que usamos anteriormente es similar a la siguiente:

Screenshot of the View helper method with the text View Result View (string view Name, object model).

El primer parámetro para el método auxiliar View() es el nombre del archivo de plantilla de vista que queremos usar para representar la respuesta HTML. El segundo parámetro es un objeto de modelo que contiene los datos que necesita la plantilla de vista para representar la respuesta HTML.

En nuestro método de acción Index() llamamos al método auxiliar View() e indicamos que queremos representar una lista HTML de las comidas mediante una plantilla de vista "Index". Pasamos a la plantilla de vista una secuencia de objetos Dinner para generar la lista a partir de:

//
    // GET: /Dinners/

    public ActionResult Index() {
    
        var dinners = dinnerRepository.FindUpcomingDinners().ToList();
        
        return View("Index", dinners);
    }

Dentro del método de acción Details() intentamos recuperar un objeto Dinner mediante el identificador proporcionado en la dirección URL. Si se encuentra una comida válida, llamamos al método auxiliar View(), lo que indica que queremos usar una plantilla de vista "Details" para representar el objeto Dinner recuperado. Si se solicita una comida no válida, se representa un mensaje de error útil que indica que la comida no existe mediante una plantilla de vista "NotFound" (y una versión sobrecargada del método auxiliar View() que simplemente toma el nombre de la plantilla):

//
    // GET: /Dinners/Details/2

    public ActionResult Details(int id) {

        Dinner dinner = dinnerRepository.FindDinner(id);

        if (dinner == null)
            return View("NotFound");
        else
            return View("Details", dinner);
    }

Ahora vamos a implementar las plantillas de vista "NotFound", "Details" e "Index".

Implementación de la plantilla de vista "NotFound"

Comenzaremos implementando la plantilla de vista "NotFound", que muestra un mensaje de error descriptivo que indica que no se encuentra la comida solicitada.

Crearemos una nueva plantilla de vista colocando el cursor de texto dentro de un método de acción de controlador y, a continuación, haremos clic con el botón derecho y elegiremos el comando de menú "Agregar vista" (también podemos ejecutar este comando escribiendo Ctrl-M, Ctrl-V):

Screenshot of the project with the right-click menu item Add View highlighted in blue and circled in red.

Se abrirá un cuadro de diálogo "Agregar vista" como se muestra a continuación. De forma predeterminada, el cuadro de diálogo rellenará previamente el nombre de la vista que se va a crear para que coincida con el nombre del método de acción en el que se inició el cursor (en este caso, "Details"). Dado que queremos implementar primero la plantilla "NotFound", reemplazaremos este nombre de vista y lo estableceremos en su lugar como "NotFound":

Screenshot of the Add View window with the View name field set to Not Found, the Select master page box checked, and the Content Place Holder I D set to Main Content.

Al hacer clic en el botón "Agregar", Visual Studio nos creará una nueva plantilla de vista "NotFound.aspx" dentro del directorio "\Views\Dinners" (que también creará si aún no existe):

Screenshot of the Solution Explorer window folder hierarchy with the Not Found dot a s p x file highlighted in blue.

También abrirá nuestra nueva plantilla de vista "NotFound.aspx" en el editor de código:

Screenshot of the code editor window with the Not Found dot a s p x file opened within the code editor.

Las plantillas de vista tienen de forma predeterminada dos "regiones de contenido" donde podemos agregar contenido y código. La primera nos permite personalizar el "título" de la página HTML devuelta. La segunda nos permite personalizar el "contenido principal" de la página HTML devuelta.

Para implementar nuestra plantilla de vista "NotFound", agregaremos contenido básico:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Dinner Not Found
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Dinner Not Found</h2>

    <p>Sorry - but the dinner you requested doesn't exist or was deleted.</p>

</asp:Content>

Después, podemos probarlo dentro del explorador. Para ello, solicitemos la dirección URL "/Dinners/Details/9999". Esto hará referencia a una comida que no existe actualmente en la base de datos y hará que nuestro método de acción DinnersController.Details() represente nuestra plantilla de vista "NotFound":

Screenshot of the My MVC Application window with the / Dinners / Details / 9999 U R L in the address box circled in red.

Una cosa que observará en la captura de pantalla anterior es que nuestra plantilla de vista básica ha heredado un montón de HTML que rodea el contenido principal en la pantalla. Esto se debe a que nuestra plantilla de vista usa una plantilla de "página maestra" que nos permite aplicar un diseño coherente en todas las vistas del sitio. Seguiremos analizando cómo funcionan las páginas maestras en una parte posterior de este tutorial.

Implementación de la plantilla de vista "Details"

Ahora vamos a implementar la plantilla de vista "Details", que generará HTML para un único modelo Dinner.

Para ello, colocaremos el cursor de texto en el método de acción Details, haremos clic con el botón derecho y elegiremos el comando de menú "Agregar vista" (o presionaremos Ctrl-M, Ctrl-V):

Screenshot of the code editor window showing the right click menu item Add View dot dot dot highlighted in red.

Se abrirá el cuadro de diálogo "Agregar vista". Mantendremos el nombre de vista predeterminado ("Details"). También seleccionaremos la casilla "Crear una vista fuertemente tipada" en el cuadro de diálogo y seleccionaremos (con la lista desplegable del cuadro combinado) el nombre del tipo de modelo que pasamos del controlador a la vista. Para esta vista, pasamos un objeto Dinner (el nombre completo de este tipo es: "NerdDinner.Models.Dinner"):

Screenshot of the Add View window with the View content dropdown set to Details and the View data class set to Nerd Dinner dot Models dot Dinner.

A diferencia de la plantilla anterior, donde decidimos crear una "vista vacía", esta vez elegiremos "aplicar scaffolding" automáticamente a la vista mediante una plantilla "Details". Podemos indicarlo cambiando la lista desplegable "Ver contenido" en el cuadro de diálogo anterior.

"Aplicar scaffolding" generará una implementación inicial de nuestra plantilla de vista de detalles basada en el objeto Dinner que estamos pasando a ella. Se esta manera podemos empezar a trabajar rápidamente en nuestra implementación de plantillas de vista.

Al hacer clic en el botón "Agregar", Visual Studio nos creará un nuevo archivo de plantilla de vista "Details.aspx" en nuestro directorio "\Views\Dinners":

Screenshot of the Solution Explorer window showing the folder hierarchy with the Dinners folder highlighted in blue.

También abrirá nuestra nueva plantilla de vista "Details.aspx" en el editor de código. Contendrá una implementación de scaffolding inicial de una vista de detalles basada en un modelo de comida. El motor de scaffolding usa la reflexión de .NET para examinar las propiedades públicas expuestas en la clase pasada y agregará el contenido adecuado en función de los tipos que encuentre:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Details
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Details</h2>

    <fieldset>
        <legend>Fields</legend>
        <p>
            DinnerID:
            <%=Html.Encode(Model.DinnerID) %>
        </p>
        <p>
            Title:
            <%=Html.Encode(Model.Title) %>
        </p>
        <p>
            EventDate:
            <%= Html.Encode(String.Format("{0:g}", Model.EventDate)) %>
        </p>
        <p>
            Description:
            <%=Html.Encode(Model.Description) %>
        </p>
        <p>
            HostedBy:
            <%=Html.Encode(Model.HostedBy) %>
        </p>
        <p>
            ContactPhone:
            <%=Html.Encode(Model.ContactPhone) %>
        </p>
        <p>
            Address:
            <%=Html.Encode(Model.Address) %>
        </p>
        <p>
            Country:
            <%=Html.Encode(Model.Country) %>
        </p>
        <p>
            Latitude:
            <%= Html.Encode(String.Format("{0:F}",Model.Latitude)) %>
        </p>
        <p>
            Longitude:
            <%= Html.Encode(String.Format("{0:F}",Model.Longitude)) %>
        </p>
    </fieldset>
    
    <p>
        <%=Html.ActionLink("Edit","Edit", new { id=Model.DinnerID }) %>|
        <%=Html.ActionLink("Back to List", "Index") %>
    </p>
    
</asp:Content>

Podemos solicitar la dirección URL "/Dinners/Details/1" para ver el aspecto de esta implementación de scaffolding de "detalles" en el explorador. Con esta dirección URL se mostrará una de las comidas que hemos agregado manualmente a nuestra base de datos cuando la creamos por primera vez:

Screenshot of the application response window showing the / Dinners / Details / 1 U R L circled in red in the address box.

Esto nos pone en marcha rápidamente y nos proporciona una implementación inicial de nuestra vista de Details.aspx. A continuación, podemos ajustarla para personalizar la interfaz de usuario según nuestros gustos.

Al examinar la plantilla Details.aspx más detenidamente, veremos que contiene código HTML estático, así como código de representación incrustado. Los nuggets de código <% %> ejecutan código cuando se representa la plantilla de vista y los nuggets de código <%= %> ejecutan el código contenido en ellos y representan el resultado en el flujo de salida de la plantilla.

Podemos escribir código en nuestra vista que acceda al objeto de modelo "Dinner" que se pasó desde nuestro controlador mediante una propiedad "Model" fuertemente tipada. Visual Studio nos proporciona código completo (IntelliSense) al acceder a esta propiedad "Model" dentro del editor:

Screenshot of the code editor window showing a dropdown list with the item Description highlighted in blue.

Vamos a realizar algunos ajustes para que el origen de la plantilla de vista Details final tenga el siguiente aspecto:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Dinner: <%=Html.Encode(Model.Title) %>
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2><%=Html.Encode(Model.Title) %></h2>
    <p>
        <strong>When:</strong> 
        <%=Model.EventDate.ToShortDateString() %> 

        <strong>@</strong>
        <%=Model.EventDate.ToShortTimeString() %>
    </p>
    <p>
        <strong>Where:</strong> 
        <%=Html.Encode(Model.Address) %>,
        <%=Html.Encode(Model.Country) %>
    </p>
     <p>
        <strong>Description:</strong> 
        <%=Html.Encode(Model.Description) %>
    </p>       
    <p>
        <strong>Organizer:</strong> 
        <%=Html.Encode(Model.HostedBy) %>
        (<%=Html.Encode(Model.ContactPhone) %>)
    </p>
    
    <%= Html.ActionLink("Edit Dinner", "Edit", new { id=Model.DinnerID })%> |
    <%= Html.ActionLink("Delete Dinner","Delete", new { id=Model.DinnerID})%>   
     
</asp:Content>

Al acceder a la dirección URL "/Dinners/Details/1" de nuevo, se representa como se indica a continuación:

Screenshot of the application response window showing the new stylization of the dot NET Futures view.

Implementación de la plantilla de vista "Index"

Ahora vamos a implementar la plantilla de vista "Index", que generará una lista de las próximas comidas. Para ello, colocaremos el cursor de texto dentro del método de acción Index, haremos clic con el botón derecho y elegiremos el comando de menú "Agregar vista" (o presionaremos Ctrl-M, Ctrl-V).

En el cuadro de diálogo "Agregar vista", mantendremos la plantilla de vista denominada "Index" y seleccionaremos la casilla "Crear una vista fuertemente tipada". Esta vez elegiremos generar automáticamente una plantilla de vista "List" y seleccionaremos "NerdDinner.Models.Dinner" como tipo de modelo pasado a la vista (que, dado que hemos indicado que estamos creando una scaffolding "lista", hará que el cuadro de diálogo Agregar vista suponga que pasamos una secuencia de objetos Dinner de nuestro controlador a la vista):

Screenshot of the Add View window with the View name set to Index, the Create a strongly-typed view box ticked, and the Select master page box ticked.

Al hacer clic en el botón "Agregar", Visual Studio nos creará un nuevo archivo de plantilla de vista "Index.aspx" dentro de nuestro directorio "\Views\Dinners". Se aplicará "scaffolding" a una implementación inicial dentro de ella que proporcionará una lista de tabla HTML de las comidas que pasemos a la vista.

Cuando se ejecuta la aplicación y se accede a la dirección URL "/Dinners/", se representa la lista de comidas de la siguiente manera:

Screenshot of the application response window showing the list of dinners in a grid layout after the Add View update.

La solución de tabla anterior nos proporciona un diseño similar a la cuadrícula de datos de las comidas, que no es lo que queremos para nuestra lista de comidas orientadas al consumidor. Podemos actualizar la plantilla de vista Index.aspx y modificarla para enumerar menos columnas de datos y usar un elemento <ul> para representarlos en lugar de una tabla mediante el código siguiente:

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Upcoming Dinners</h2>

    <ul>
        <% foreach (var dinner in Model) { %>
        
            <li>                 
                <%=Html.Encode(dinner.Title) %>            
                on 
                <%=Html.Encode(dinner.EventDate.ToShortDateString())%>
                @
                <%=Html.Encode(dinner.EventDate.ToShortTimeString())%>
            </li>
            
        <% } %>
    </ul>
    
</asp:Content>

Estamos usando la palabra clave "var" dentro de la instrucción foreach anterior a medida que recorremos en bucle cada comida de nuestro modelo. Los que no estén familiarizados con C# 3.0 podrían pensar que el uso de "var" significa que el objeto dinner está enlazado en tiempo de ejecución. En cambio, significa que el compilador usa la inferencia de tipos en la propiedad "Model" fuertemente tipada (que es de tipo "IEnumerable<Dinner>") y compila la variable local "dinner" como tipo Dinner, lo que significa que obtenemos IntelliSense completo y la comprobación en tiempo de compilación en bloques de código:

Screenshot of the code editor window showing a dropdown menu with the Address list item highlighted in a gray dotted box.

Cuando llegamos a la actualización en la dirección URL de /Dinners en nuestro explorador, nuestra vista actualizada ahora tiene el siguiente aspecto:

Screenshot of the application response window showing a list of upcoming dinners after the refresh command.

Esto es mejor, pero aún no hemos terminado. Nuestro último paso es permitir que los usuarios finales hagan clic en comidas individuales de la lista y vean detalles sobre ellas. Lo implementaremos mediante la representación de elementos de hipervínculo HTML que se vinculan al método de acción Details en nuestro DinnersController.

Podemos generar estos hipervínculos dentro de la vista Index de una de estas dos maneras. La primera consiste en crear manualmente elementos HTML <a> como el siguiente, donde insertamos bloques de <% %> dentro de un elemento HTML <a>:

Screenshot of the code editor window with the a class and percent block text highlighted and circled in red.

Un enfoque alternativo es aprovechar el método auxiliar integrado "Html.ActionLink()" en ASP.NET MVC que admite la creación mediante programación de un elemento HTML <a> que se vincula a otro método de acción en un controlador:

<%= Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) %>

El primer parámetro para el método auxiliar Html.ActionLink() es el texto de vínculo que se va a mostrar (en este caso, el título de la comida), el segundo parámetro es el nombre de la acción del controlador al que queremos generar el vínculo (en este caso, el método Details) y el tercer parámetro es un conjunto de parámetros para enviar a la acción (implementado como tipo anónimo con nombre de propiedad/valores). En este caso, especificamos el parámetro "id" de la comida que queremos vincular y, dado que la regla de enrutamiento de direcciones URL predeterminada en ASP.NET MVC es "{Controller}/{Action}/{id}", el método auxiliar Html.ActionLink() generará la salida siguiente:

<a href="/Dinners/Details/1">.NET Futures</a>

Para nuestra vista de Index.aspx usaremos el enfoque del método auxiliar Html.ActionLink() y tendremos cada comida en el vínculo de lista a la dirección URL de detalles adecuada:

<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Upcoming Dinners
</asp:Content>

<asp:Content ID="Main" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Upcoming Dinners</h2>

    <ul>
        <% foreach (var dinner in Model) { %>
        
            <li>     
                <%=Html.ActionLink(dinner.Title, "Details", new { id=dinner.DinnerID }) %>
                on 
                <%=Html.Encode(dinner.EventDate.ToShortDateString())%>
                @
                <%=Html.Encode(dinner.EventDate.ToShortTimeString())%>
            </li>
            
        <% } %>
    </ul>
</asp:Content>

Y ahora, cuando llegamos a la dirección URL /Dinners, nuestra lista de comidas es similar a la siguiente:

Screenshot of the application response window that shows the upcoming dinners list with new links corresponding to the list items.

Al hacer clic en cualquiera de las comidas de la lista, navegaremos para ver detalles sobre ella:

Screenshot of the application response window that shows the selected list item and details corresponding to it as entered into the database.

Nomenclatura basada en convenciones y estructura del directorio \Views

Las aplicaciones ASP.NET MVC usan de forma predeterminada una estructura de nomenclatura de directorios basada en convenciones al resolver las plantillas de vista. Esto les evita a los desarrolladores tener que calificar completamente una ruta de acceso de ubicación al hacer referencia a vistas desde una clase Controller. De forma predeterminada, ASP.NET MVC busca el archivo de plantilla de vista dentro del directorio *\Views[ControllerName]* debajo de la aplicación.

Por ejemplo, hemos estado trabajando en la clase DinnersController, que hace referencia explícitamente a tres plantillas de vista: "Index", "Details" y "NotFound". ASP.NET MVC buscará estas vistas de forma predeterminada en el directorio \Views\Dinners debajo del directorio raíz de la aplicación:

Screenshot of the Solution Explorer window showing the folder hierarchy with the Dinners folder highlighted in a blue rectangle.

Observe más arriba cómo hay actualmente tres clases de controlador dentro del proyecto (DinnersController, HomeController y AccountController: los dos últimos se agregaron de forma predeterminada al crear el proyecto) y hay tres subdirectorios (uno para cada controlador) dentro del directorio \Views.

Las vistas a las que se hace referencia desde los controladores Home y Accounts resolverán automáticamente sus plantillas de vista de los directorios \Views\Home y \Views\Account correspondientes. El subdirectorio \Views\Shared proporciona una manera de almacenar plantillas de vista que se vuelven a usar en varios controladores dentro de la aplicación. Cuando ASP.NET MVC intenta resolver una plantilla de vista, primero comprobará dentro del directorio específico \Views[Controller] y, si no encuentra la plantilla de vista, buscará dentro del directorio \Views\Shared.

En lo que respecta a asignar nombres a plantillas de vista individuales, se recomienda que la plantilla de vista comparta el mismo nombre que el método de acción que lo hizo representar. Por ejemplo, encima del método de acción "Index" se usa la vista "Index" para representar el resultado de la vista y el método de acción "Details" usa la vista "Details" para representar sus resultados. Así se ve rápidamente qué plantilla está asociada a cada acción.

Los desarrolladores no necesitan especificar explícitamente el nombre de la plantilla de vista cuando esta tiene el mismo nombre que el método de acción que se invoca en el controlador. En su lugar, podemos simplemente pasar el objeto de modelo al método auxiliar "View()" (sin especificar el nombre de la vista) y ASP.NET MVC deducirá automáticamente que queremos usar la plantilla de vista \Views[ControllerName][ActionName] en el disco para representarlo.

Esto nos permite limpiar un poco el código del controlador y evitar duplicar el nombre en nuestro código:

public class DinnersController : Controller {

    DinnerRepository dinnerRepository = new DinnerRepository();

    //
    // GET: /Dinners/

    public ActionResult Index() {

        var dinners = dinnerRepository.FindUpcomingDinners().ToList();

        return View(dinners);
    }

    //
    // GET: /Dinners/Details/2

    public ActionResult Details(int id) {

        Dinner dinner = dinnerRepository.GetDinner(id);

        if (dinner == null)
            return View("NotFound");
        else
            return View(dinner);
    }
}

El código anterior es todo lo que se necesita para implementar una agradable experiencia de descripción y detalles de la comida para el sitio.

siguiente paso

Ya hemos creado una agradable experiencia de exploración de la comida.

Ahora vamos a habilitar la compatibilidad con la edición de formularios de datos CRUD (del inglés, crear, leer, actualizar y eliminar).