Compartir a través de


Reutilizar la interfaz de usuario con páginas maestras y parciales

de Microsoft

Descargar PDF

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

En el paso 7 se examinan las distintas formas en que se puede aplicar el "principio DRY" en nuestras plantillas de vista para eliminar la duplicación de código, para lo que se usan plantillas de vista de líneas de código parcialmente ejecutada y páginas maestras.

Si se usa MVC 3 de ASP.NET, es aconsejable seguir los tutoriales Introducción a MVC 3 o MVC Music Store.

Paso 7 de NerdDinner: líneas de código parcialmente ejecutadas y páginas maestras

Una de las filosofías de diseño de MVC de ASP.NET es el principio "Una vez y solo una" (que habitualmente se denomina "DRY"). Los diseños DRY ayudan a eliminar la duplicación del código y de la lógica, lo que, en último término, hace que las aplicaciones se compilen más rápidamente y sean más fáciles de mantener.

Ya se ha visto el principio DRY aplicado en varios escenarios de NerdDinner. Algunos ejemplos: nuestra lógica de validación se implementa en nuestra capa de modelo, lo que permite aplicarla en los escenarios de edición y creación de nuestro controlador; vamos a reutilizar la plantilla de vista "NotFound" en los métodos de acción Edit, Details y Delete; vamos a usar un patrón de nomenclatura convencional con nuestras plantillas de vista, lo que elimina la necesidad de especificar explícitamente el nombre al llamar al método auxiliar View() y vamos a usar la clase DinnerFormViewModel para los escenarios de acción Edit y Create.

Ahora echemos un vistazo a las distintas formas en que podemos aplicar el "principio DRY" en nuestras plantillas de vista para eliminar también la duplicación de código también en ellas.

Volver a visitar nuestras plantillas de vista de edición y creación

Actualmente se usan dos plantillas de vista diferentes ( "Edit.aspx" y "Create.aspx") para mostrar la interfaz de usuario del formulario de Dinner. Una comparación visual rápida de ellas resalta lo similares que son. A continuación puede ver cómo es el formulario de creación:

Screenshot of the My M V C Application page. The Host a Dinner form is shown.

Y este es el aspecto del formulario de edición:

Screenshot of the My M V C Application paage is shown. The Edit form is shown.

¿No hay mucha diferencia? El título y el texto del encabezado son distintos, pero el diseño del formulario y los controles de entrada son idénticos.

Si se abren las plantillas de vista "Edit.aspx" y "Create.aspx", se puede apreciar que contienen un diseño de formulario y un código de controles de entrada idénticos. Esta duplicación significa que acabaremos teniendo que realizar los cambios dos veces cada vez que introduzcamos o cambiemos una nueva propiedad de Dinner (y no queremos que eso pase).

Uso de plantillas de vista de líneas de código parcialmente ejecutadas

ASP.NET MVC admite la capacidad de definir plantillas de "vista de líneas de código parcialmente ejecutadas" que se pueden usar para encapsular la lógica de representación de vistas para una parte secundaria de una página. Las "líneas de código parcialmente ejecutadas" proporcionan una forma útil de definir la lógica de representación de vistas una vez y, después, volver a usarla en varios lugares de una aplicación.

Para ayudar a aplicar el principio "DRY" a nuestra duplicación de la plantilla de vistas Edit.aspx y Create.aspx, podemos crear una plantilla de vista de líneas de código parcialmente ejecutadas denominada "DinnerForm.ascx" que encapsula el diseño del formulario y los elementos de entrada comunes a ambos. Para ello, haga clic con el botón secundario en el directorio /Views/Dinners y elija el comando de menú "Agregar->Vista":

Screenshot of the Solution Explorer navigation tree. Dinner is highlighted. Add and View are selected. View is highlighted.

Se mostrará el cuadro de diálogo "Agregar vista". Asignaremos el nombre "DinnerForm" a la nueva vista que queremos crear, seleccionaremos la casilla "Crear una vista de líneas de código parcialmente ejecutadas" en el cuadro de diálogo e indicaremos que la usaremos en la clase DinnerFormViewModel:

Screenshot of the Add View dialog. Create a partial view is selected and highlighted.

Al hacer clic en el botón "Agregar", Visual Studio nos creará la plantilla de vista "DinnerForm.ascx" en el directorio "\Views\Dinners".

Luego, podemos copiar o pegar el código de control de entrada o diseño de formulario duplicados de las plantillas de vista Edit.aspx o Create.aspx en nuestra nueva plantilla de vista de líneas de código parcialmente ejecutadas "DinnerForm.ascx":

<%= Html.ValidationSummary("Please correct the errors and try again.") %>

<% using (Html.BeginForm()) { %>

    <fieldset>
        <p>
            <label for="Title">Dinner Title:</label>
            <%= Html.TextBox("Title", Model.Dinner.Title) %>
            <%=Html.ValidationMessage("Title", "*") %>
        </p>
        <p>
            <label for="EventDate">Event Date:</label>
            <%= Html.TextBox("EventDate", Model.Dinner.EventDate) %>
            <%= Html.ValidationMessage("EventDate", "*") %>
        </p>
        <p>
            <label for="Description">Description:</label>
            <%= Html.TextArea("Description", Model.Dinner.Description) %>
            <%= Html.ValidationMessage("Description", "*") %>
        </p>
        <p>
            <label for="Address">Address:</label>
            <%= Html.TextBox("Address", Model.Dinner.Address) %>
            <%= Html.ValidationMessage("Address", "*") %>
        </p>
        <p>
            <label for="Country">Country:</label>
            <%= Html.DropDownList("Country", Model.Countries) %>                
            <%= Html.ValidationMessage("Country", "*") %>
        </p>
        <p>
            <label for="ContactPhone">Contact Phone #:</label>
            <%= Html.TextBox("ContactPhone", Model.Dinner.ContactPhone) %>
            <%= Html.ValidationMessage("ContactPhone", "*") %>
        </p>
            
        <p>
            <input type="submit" value="Save"/>
        </p>
    </fieldset>
    
<% } %>

Después, podemos actualizar nuestras plantillas de vista Edit y Create para llamar a la plantilla de línea de código parcialmente ejecutada DinnerForm y eliminar la duplicación de formularios. Para ello, llamamos a Html.RenderPartial("DinnerForm") desde nuestras plantillas de vista:

Create.aspx
<asp:Content ID="Title" ContentPlaceHolderID="TitleContent" runat="server">
    Host a Dinner
</asp:Content>

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

    <h2>Host a Dinner</h2>

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

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

    <h2>Edit Dinner</h2>

    <% Html.RenderPartial("DinnerForm"); %>

</asp:Content>

Puede calificar explícitamente la ruta de acceso de la plantilla parcial que desee al llamar a Html.RenderPartial (por ejemplo: ~Views/Dinners/DinnerForm.ascx"). Sin embargo, en nuestro código anterior, vamos a aprovechar patrón de nomenclatura basado en convención dentro en MVC de ASP.NET y simplemente especificaremos "DinnerForm" como nombre de la línea de código parcialmente ejecutada que se va a representar. Cuando lo hagamos, MVC de ASP.NET mirará primero en el directorio de vistas basadas en convención (en el caso de DinnersController sería /Views/Dinners). Si no encuentra la plantilla de línea de código parcialmente ejecutada allí, la buscará en el directorio /Views/Shared.

Cuando se llama a Html.RenderPartial() con solo el nombre de la vista parcial, el MVC de ASP.NET pasará a la vista parcial los mismos objetos de diccionario Model y ViewData que ha usado la plantilla de vista que ha realizado la llamada. Como alternativa, hay versiones sobrecargadas de Html.RenderPartial() que permiten pasar un objeto Model alternativo o un diccionario ViewData para que lo use la vista de líneas de código parcialmente ejecutadas. Esto es útil para aquellos escenarios en los que solo se desea pasar un subconjunto de Model o ViewModel completo.

Tema secundario: ¿Por qué <% %> en lugar de <%= %>?
Algo que puede haber observado con el código anterior es que estamos usando un bloque <% %>, en lugar de un bloque<%= %> al llamar a Html.RenderPartial(). Los bloques <%= %> en ASP.NET indican que un desarrollador quiere representar un valor especificado (por ejemplo: <%= "Hello" %> representaría "Hello"). En su lugar, los bloques <% %> indican que el desarrollador quiere ejecutar código y que cualquier salida representada en él debe realizarse explícitamente (por ejemplo: <% Response.Write("Hello") %>. La razón por lo que usamos un bloque <% %> con nuestro código de Html.RenderPartial anterior es que el método Html.RenderPartial() no devuelve una cadena, en lugar genera el contenido directamente en el flujo de salida de la plantilla que realiza la llamada. Lo hace para mejorar rendimiento y, al hacerlo, evita la necesidad de crear un objeto string temporal (potencialmente muy grande). Así se reduce el uso de memoria y se mejora el rendimiento general de la aplicación. Un error común al usar Html.RenderPartial() es olvidar agregar un punto y coma al final de la llamada cuando se encuentra dentro de un bloque <% %>. Por ejemplo, este código provocará un error del compilador: <% Html.RenderPartial("DinnerForm") %> Debe escribir: <% Html.RenderPartial("DinnerForm"); %> Esto se debe a que los bloques <%%> son instrucciones de código autocontenidas y, al usar instrucciones de código de C#, es necesario terminar con un punto y coma.

Uso de plantillas de vista de líneas de código parcialmente ejecutadas para aclarar el código

Hemos creado la plantilla de vista de líneas de código parcialmente ejecutadas "DinnerForm" para evitar duplicar la lógica de representación de vistas en varios lugares. Esta es la razón más común para crear plantillas de vista parcial.

A veces tiene sentido crear vistas de líneas de código parcialmente ejecutadas, incluso cuando se les llama en un solo lugar. Las plantillas de vista muy complicadas a menudo pueden resultar mucho más fáciles de leer cuando se extrae su lógica de representación de vistas y se divide en una o varias plantillas de línea de código parcialmente ejecutada.

Por ejemplo, considere el siguiente fragmento de código del archivo Site.master de nuestro proyecto (que veremos en breve). El código es relativamente sencillo de leer, en parte porque la lógica para mostrar un vínculo de inicio de sesión o cierre de sesión en la parte superior derecha de la pantalla se encapsula dentro de la línea de código parcialmente ejecutada "LogOnUserControl":

<div id="header">
    <div id="title">
        <h1>My MVC Application</h1>
    </div>
      
    <div id="logindisplay">
        <% Html.RenderPartial("LogOnUserControl"); %>
    </div> 
    
    <div id="menucontainer">
    
        <ul id="menu">              
            <li><%=Html.ActionLink("Home", "Index", "Home")%></li>
            <li><%=Html.ActionLink("About", "About", "Home")%></li>
        </ul>
    </div>
</div>

Cada vez que se encuentre confundido al intentar entender el marcado html/code de una plantilla de vista, piense si no sería más claro si parte de él fuera extraído y refactorizado en vistas de líneas de código parcialmente ejecutadas con nombres adecuados.

Páginas maestras

Además de admitir vistas de líneas de código parcialmente ejecutadas, MVC de ASP.NET también admite la capacidad de crear plantillas de "página maestra" que se pueden usar para definir el diseño común y el html de nivel superior de un sitio. Luego, se pueden agregar controles de marcador de posición de contenido a la página maestra para identificar las regiones reemplazables que las vistas pueden invalidar o "rellenar". Esto proporciona una manera muy eficaz (y DRY) de aplicar un diseño común a través de una aplicación.

De forma predeterminada, a los nuevos proyectos de MVC de ASP.NET se les agrega automáticamente una plantilla de página maestra. Esta página maestra se denomina "Site.master" y se encuentra en la carpeta \Views\Shared\:

Screenshot of the Nerd Dinner navigation tree. Site Master is highlighted and selected.

El archivo Site.master predeterminado es similar al siguiente. Define el html externo del sitio, junto con un menú para desplazarse, en la parte superior. Contiene dos controles de marcador de posición de contenido reemplazables (uno para el título y el otro para el lugar en el que debe reemplazarse el contenido principal de una página):

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">
    <title>
       <asp:ContentPlaceHolder ID="TitleContent" runat="server" />
    </title>
   <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <div class="page">

        <div id="header">
            <div id="title">
                <h1>My MVC Application</h1>
            </div>
              
            <div id="logindisplay">
                <% Html.RenderPartial("LogOnUserControl"); %>
            </div> 
            
            <div id="menucontainer">

                <ul id="menu">              
                    <li><%=Html.ActionLink("Home", "Index", "Home")%></li>
                    <li><%=Html.ActionLink("About", "About", "Home")%></li>
                </ul>
            
            </div>
        </div>

        <div id="main">
            <asp:ContentPlaceHolder ID="MainContent" runat="server" />
        </div>
    </div>
</body>
</html>

Todas las plantillas de vista que hemos creado para la aplicación NerdDinner ("List", "Details", "Edit", "Create", "NotFound", etc.) se han basado en la plantilla Site.master. Esto se indica a través del atributo "MasterPageFile" que se agregó de forma predeterminada a la primera directiva <% @ Page %> al crear las vistas mediante el cuadro de diálogo "Agregar vista":

<%@ Page Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerViewModel>" MasterPageFile="~/Views/Shared/Site.Master" %>

Esto significa es que se puede cambiar el contenido de Site.master y que los cambios se apliquen y se utilicen automáticamente cuando se representa cualquiera de las plantillas de vista.

Vamos a actualizar la sección de encabezado de Site.master para que el encabezado de la aplicación sea "NerdDinner", en lugar de "Mi aplicación MVC". Actualicemos también nuestro menú de navegación para que la primera pestaña sea "Buscar una cena" (administrada por el método de acción Index() de HomeController) y agreguemos una nueva pestaña llamada "Organizar una cena" (administrada por el método de acción Create() de DinnersController):

<div id="header">

    <div id="title">
        <h1>NerdDinner</h1>
    </div>

    <div id="logindisplay">
        <% Html.RenderPartial("LoginStatus"); %>
    </div> 
    
    <div id="menucontainer">
        <ul id="menu">      
           <li><%=Html.ActionLink("Find Dinner", "Index", "Home")%></li>
           <li><%=Html.ActionLink("Host Dinner", "Create", "Dinners")%></li>
           <li><%=Html.ActionLink("About", "About", "Home")%></li>   
        </ul>
    </div>
</div>

Cuando guardemos el archivo Site.master y actualicemos el explorador web, veremos que los cambios realizados en el encabezado se muestran en todas las vistas de la aplicación. Por ejemplo:

Screenshot of the Nerd Dinner Upcoming Dinners list page.

Y con la dirección URL /Dinners/Edit/[id]:

Screenshot of the Nerd Dinner Edit form page is shown.

siguiente paso

Las líneas de código parcialmente ejecutadas y las páginas maestras proporcionan opciones muy flexibles que permiten organizar las vistas limpiamente. Se dará cuenta de que le ayudan a evitar duplicar el contenido/código de las vistas y que facilitan la lectura y mantenimiento de plantillas de vistas.

Volvamos ahora al escenario de listado que creamos anteriormente y habilitemos el soporte técnico de la paginación escalable.