Páginas maestras anidadas (C#)

por Scott Mitchell

Muestra cómo anidar una página maestra dentro de otra.

Introducción

En el transcurso de los últimos nueve tutoriales hemos visto cómo implementar un diseño de todo el sitio con páginas maestras. En pocas palabras, las páginas maestras nos permiten a nosotros, los desarrolladores de páginas, definir el marcado común en la página maestra junto con regiones específicas que se pueden personalizar página de contenido por página. Los controles ContentPlaceHolder de una página maestra indican las regiones personalizables; el marcado personalizado para los controles ContentPlaceHolder se define en la página de contenido a través de controles Content.

Las técnicas de página maestra que hemos explorado hasta ahora son excelentes si tiene un único diseño que se usa en todo el sitio. Sin embargo, muchos sitios web de gran tamaño tienen un diseño de sitio personalizado en varias secciones. Por ejemplo, considere una aplicación de atención médica utilizada por el personal del hospital para administrar la información, las actividades y la facturación de los pacientes. Puede haber tres tipos de páginas web en esta aplicación:

  • Páginas específicas del miembro del personal en las que los miembros del personal pueden actualizar la disponibilidad, ver las programaciones o solicitar vacaciones.
  • Páginas específicas del paciente en las que los miembros del personal ven o editan información de un paciente específico.
  • Páginas específicas de facturación en las que los contables revisan los estados actuales de las reclamaciones e informes financieros.

Cada página puede compartir un diseño común, como un menú en la parte superior y una serie de vínculos usados con frecuencia a lo largo de la parte inferior. Pero es posible que las páginas específicas del personal, el paciente y la facturación necesiten personalizar este diseño genérico. Por ejemplo, quizás todas las páginas específicas del personal deben incluir un calendario y una lista de tareas que muestren la disponibilidad y la programación diaria del usuario que ha iniciado sesión. Quizás todas las páginas específicas del paciente necesiten mostrar el nombre, la dirección y la información del seguro del paciente cuya información se está editando.

Es posible crear estos diseños personalizados mediante páginas maestras anidadas. Para implementar el escenario anterior, comenzaríamos creando una página maestra que define el diseño de todo el sitio, el menú y el contenido del pie de página, con ContentPlaceHolders definiendo las regiones personalizables. A continuación, crearíamos tres páginas maestras anidadas, una para cada tipo de página web. Cada página maestra anidada definiría el contenido entre el tipo de páginas de contenido que usan la página maestra. En otras palabras, la página maestra anidada para páginas de contenido específicas del paciente incluiría marcado y lógica de programación para mostrar la información sobre el paciente que se está editando. Al crear una nueva página específica del paciente, la enlazaríamos a esta página maestra anidada.

Este tutorial comienza resaltando las ventajas de las páginas maestras anidadas. A continuación, muestra cómo crear y usar páginas maestras anidadas.

Nota:

Las páginas maestras anidadas son posibles desde la versión 2.0 de .NET Framework. Sin embargo, Visual Studio 2005 no incluía compatibilidad en tiempo de diseño para páginas maestras anidadas. La buena noticia es que Visual Studio 2008 ofrece una experiencia enriquecida en tiempo de diseño para páginas maestras anidadas. Si está interesado en usar páginas maestras anidadas, pero sigue usando Visual Studio 2005, consulte la entrada de blog de Scott Guthrie: Sugerencias para páginas maestras anidadas en VS 2005 Design-Time.

Las ventajas de las páginas maestras anidadas

Muchos sitios web tienen un diseño de sitio general, así como diseños más personalizados específicos de determinados tipos de páginas. Por ejemplo, en nuestra aplicación web de demostración hemos creado una sección de administración rudimentaria (las páginas de la carpeta ~/Admin). Actualmente, las páginas web de la carpeta ~/Admin usan la misma página maestra que las páginas que no están en la sección de administración (es decir, Site.master o Alternate.master, dependiendo de la selección del usuario).

Nota:

Por ahora, imaginemos que nuestro sitio tiene solo una página maestra: Site.master. Abordaremos el uso de páginas maestras anidadas con dos (o más) páginas maestras a partir de "Uso de una página maestra anidada para la sección de administración" más adelante en este tutorial.

Imagine que se nos ha pedido que personalice el diseño de las páginas de administración para incluir información adicional o vínculos que, de lo contrario, no estarían presentes en otras páginas del sitio. Hay cuatro técnicas para implementar este requisito:

  1. Agregue manualmente la información específica de administración y los vínculos a todas las páginas de contenido de la carpeta ~/Admin.
  2. Actualice la página maestra Site.master para incluir la información y vínculos específicos de la sección Administración y, a continuación, agregue código a la página maestra para mostrar u ocultar estas secciones en función de si se está visitando una de las páginas de administración.
  3. Cree una nueva página maestra específicamente para la sección Administración, copie el marcado de Site.master, agregue la información y los vínculos específicos de la sección Administración y, a continuación, actualice las páginas de contenido de la carpeta ~/Admin para usar esta nueva página maestra.
  4. Cree una página maestra anidada que se enlace a Site.master y haga que las páginas de contenido de la carpeta ~/Admin usen esta nueva página maestra anidada. Esta página maestra anidada incluiría solo la información adicional y los vínculos específicos de las páginas de administración y no tendría que repetir el marcado ya definido en Site.master.

La primera opción es la menos apetecible. El objetivo de usar páginas maestras es alejarse de tener que copiar y pegar manualmente el marcado común en nuevas páginas de ASP.NET. La segunda opción es aceptable, pero hace que la aplicación sea menos fácil de mantener, ya que se agrupa en masa las páginas maestras con un marcado que solo se muestra ocasionalmente y requiere que los desarrolladores editen la página maestra para solucionar este marcado y que tengan que recordar cuándo, exactamente, se muestra cierto marcado y cuándo está oculto. Este enfoque sería menos sostenible a medida que las personalizaciones de más y más tipos de páginas web tuvieran que ser acomodadas por esta única página maestra.

La tercera opción elimina los problemas de desorden y complejidad que surgieron con la segunda opción. Sin embargo, el principal inconveniente de la tercera opción es que nos obliga a copiar y pegar el diseño común de Site.master en la nueva página maestra específica de la sección de Administración. Si más adelante decidimos cambiar el diseño de todo el sitio, tenemos que acordarnos de cambiarlo en dos lugares.

La cuarta opción, las páginas maestras anidadas, nos ofrece lo mejor de la segunda y la tercera opción. La información de diseño de todo el sitio se mantiene en un archivo, la página maestra de nivel superior, mientras que el contenido específico de determinadas regiones se separa en archivos diferentes.

Este tutorial comienza con un vistazo a la creación y el uso de una página maestra anidada sencilla. Crearemos una nueva página maestra de nivel superior, dos páginas maestras anidadas y dos páginas de contenido. Comenzando con "Uso de una página maestra anidada para la sección de administración", examinamos la actualización de la arquitectura de página maestra existente para incluir el uso de páginas maestras anidadas. En concreto, crearemos una página maestra anidada y la usaremos para incluir contenido personalizado adicional para las páginas de contenido de la carpeta ~/Admin.

Paso 1: creación de una página maestra de nivel superior simple

Crear una página maestra anidada basada en una de las páginas maestras existentes y luego actualizar una página de contenido existente para que utilice esta nueva página maestra anidada en lugar de la página maestra de nivel superior conlleva cierta complejidad porque las páginas de contenido existentes ya esperan ciertos controles ContentPlaceHolder definidos en la página maestra de nivel superior. Por lo tanto, la página maestra anidada también debe incluir los mismos controles ContentPlaceHolder con los mismos nombres. Además, nuestra aplicación de demostración concreta tiene dos páginas maestras (Site.master y Alternate.master) que se asignan dinámicamente a una página de contenido en función de las preferencias de un usuario, lo que aumenta aún más esta complejidad. Veremos cómo actualizar la aplicación existente para usar páginas maestras anidadas más adelante en este tutorial, pero primero nos centraremos en un ejemplo sencillo de páginas maestras anidadas.

Cree una nueva carpeta denominada NestedMasterPages y agregue un nuevo archivo de página maestra a la carpeta denominada Simple.master. (Vea la ilustración 1 para ver una captura de pantalla del Explorador de soluciones después de agregar esta carpeta y archivo). Arrastre el archivo de hoja de estilos AlternateStyles.css desde el Explorador de soluciones al Diseñador. Esto agrega un elemento <link> al archivo de hoja de estilos del elemento <head>, después del cual el marcado del elemento <head> de la página maestra debe ser similar al siguiente:

<head runat="server">
 <title>Untitled Page</title> 
 <asp:ContentPlaceHolder id="head" runat="server"> 
 </asp:ContentPlaceHolder>
 <link href="../AlternateStyles.css" rel="stylesheet" type="text/css" /> 
</head>

A continuación, agregue el siguiente marcado dentro del formulario web de Simple.master:

<div id="topContent"> 
 <asp:HyperLink ID="lnkHome" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx"
 Text="Nested Master Pages Tutorial (Simple)" /> 
</div> 
<div id="mainContent"> 
 <asp:ContentPlaceHolder id="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
</div>

Este marcado muestra un vínculo titulado "Páginas maestras anidadas (simple)" en la parte superior de la página en una fuente blanca grande sobre un fondo azul marino. Debajo se encuentra el ContentPlaceHolder MainContent. En la ilustración 1 se muestra la página maestra Simple.master cuando se carga en el Diseñador de Visual Studio.

The Simple dot master master page when loaded in the Visual Studio Designer.

Ilustración 01: la página maestra anidada define contenido específico de las páginas de la sección Administración (haga clic para ver la imagen a tamaño completo)

Paso 2: creación de una página maestra anidada simple

Simple.master contiene dos controles ContentPlaceHolder: los ContentPlaceHolder MainContent que agregamos dentro del formulario web junto con el ContentPlaceHolder head en el elemento <head>. Si fuéramos a crear una página de contenido y enlazarla a Simple.master, la página de contenido tendría dos controles Content que hacen referencia a los dos ContentPlaceHolders. Del mismo modo, si creamos una página maestra anidada y la enlazamos a Simple.master, la página maestra anidada tendrá dos controles Content.

Vamos a agregar una nueva página maestra anidada a la carpeta NestedMasterPages denominada SimpleNested.master. Haga clic con el botón derecho en la carpeta NestedMasterPages y elija Agregar nuevo elemento. Esto abre el cuadro de diálogo Agregar nuevo elemento que se muestra en la ilustración 2. Seleccione el tipo de plantilla Página maestra y escriba el nombre de la nueva página maestra. Para indicar que la nueva página maestra debe ser una página maestra anidada, active la casilla "Seleccionar página maestra".

A continuación, haga clic en el botón Agregar. Se mostrará el mismo cuadro de diálogo Seleccionar una página maestra que verá al enlazar una página de contenido a una página maestra (vea la ilustración 3). Elija la página maestra Simple.master de la carpeta NestedMasterPages y haga clic en Aceptar.

Nota:

Si creó el sitio web de ASP.NET con el modelo de proyecto de aplicación web en lugar del modelo de proyecto de sitio web, no verá la casilla "Seleccionar página maestra" en el cuadro de diálogo Agregar nuevo elemento que se muestra en la ilustración 2. Para crear una página maestra anidada al usar el modelo de proyecto de aplicación web, debe elegir la plantilla Página maestra anidada (en lugar de la plantilla página maestra). Después de seleccionar la plantilla Página maestra anidada y hacer clic en Agregar, aparecerá el mismo cuadro de diálogo Seleccionar una página maestra que se muestra en la ilustración 3.

Check the

Ilustración 02: active la casilla "Seleccionar página maestra" para agregar una página maestra anidada (haga clic para ver la imagen a tamaño completo)

Bind the Nested Master Page to the Simple.master Master Page

Ilustración 03: enlace la página maestra anidada a la página maestra Simple.master (haga clic para ver la imagen a tamaño completo)

El marcado declarativo de la página maestra anidada, que se muestra a continuación, contiene dos controles Content que hacen referencia a los dos controles ContentPlaceHolder de la página maestra de nivel superior.

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNested.master.cs" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 </asp:Content>

Excepto para la directiva <%@ Master %>, el marcado declarativo inicial de la página maestra anidada es idéntico al marcado que se genera inicialmente al enlazar una página de contenido a la misma página maestra de nivel superior. Al igual que la directiva <%@ Page %> de una página de contenido, la directiva <%@ Master %> aquí incluye un atributo MasterPageFile que especifica la página maestra principal de la página maestra anidada. La principal diferencia entre la página maestra anidada y una página de contenido enlazada a la misma página maestra de nivel superior es que la página maestra anidada puede incluir controles ContentPlaceHolder. Los controles ContentPlaceHolder de la página maestra anidada definen las regiones donde las páginas de contenido pueden personalizar el marcado.

Actualice esta página maestra anidada para que muestre el texto "Hello, from SimpleNested!" en el control Content que corresponde al control ContentPlaceHolder MainContent.

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
</asp:Content>

Después de realizar esta adición, guarde la página maestra anidada y agregue una nueva página de contenido a la carpeta NestedMasterPages denominada Default.aspx y enlácela a la página maestra SimpleNested.master. Al agregar esta página, es posible que se sorprenda al ver que no contiene controles Content (vea la ilustración 4). Una página de contenido solo puede acceder a los ContentPlaceHolders de su página maestra principal. SimpleNested.master no contiene ningún control ContentPlaceHolder; por lo tanto, cualquier página de contenido enlazada a esta página maestra no puede contener ningún control Content.

The New Content Page Contains No Content Controls

Ilustración 04: la página Nuevo contenido no contiene controles Content (haga clic para ver la imagen a tamaño completo)

Lo que debemos hacer es actualizar la página maestra anidada (SimpleNested.master) para incluir controles ContentPlaceHolder. Normalmente, querrá que las páginas maestras anidadas incluyan un ContentPlaceHolder para cada ContentPlaceHolder definido por su página maestra principal, lo que permite que su página maestra secundaria o página de contenido funcione con cualquiera de los controles ContentPlaceHolder de la página maestra de nivel superior.

Actualice la página maestra SimpleNested.master para incluir un ContentPlaceHolder en sus dos controles Content. Asigne a los controles ContentPlaceHolder el mismo nombre que el control ContentPlaceHolder al que hace referencia su control Content. Es decir, agregue un control ContentPlaceHolder denominado MainContent al control Content en SimpleNested.master que haga referencia al ContentPlaceHolder MainContent en Simple.master. Haga lo mismo en el control Content que hace referencia al ContentPlaceHolder head.

Nota:

Aunque se recomienda nombrar los controles ContentPlaceHolder de la página maestra anidada igual que los ContentPlaceHolders de la página maestra de nivel superior, esta simetría de nombres no es necesaria. Puede asignar a los controles ContentPlaceHolder en la página maestra anidada cualquier nombre que desee. Sin embargo, es más fácil recordar qué ContentPlaceHolders corresponden a qué regiones de la página si la página maestra de nivel superior y las páginas maestras anidadas usan los mismos nombres.

Después de realizar estas adiciones, el marcado declarativo de la página maestra SimpleNested.master debe ser similar al siguiente:

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master"AutoEventWireup="false" CodeFile="SimpleNested.master.cs" Inherits="NestedMasterPages_SimpleNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <p>Hello, from SimpleNested!</p>
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

Elimine la página de contenido Default.aspx que acabamos de crear y, a continuación, vuelva a agregarla, enlazándola a la página maestra SimpleNested.master. Esta vez, Visual Studio agrega dos controles Content a Default.aspx, haciendo referencia a los ContentPlaceHolders ahora definidos en SimpleNested.master (vea la ilustración 6). Agregue el texto "Hello, from Default.aspx!" en el control Content que hace referencia a MainContent.

En la ilustración 5 se muestran las tres entidades implicadas ( Simple.master, SimpleNested.master y Default.aspx ) y cómo se relacionan entre sí. Como se muestra en el diagrama, la página maestra anidada implementa controles Content para el ContentPlaceHolder de su elemento primario. Si estas regiones deben ser accesibles para la página de contenido, la página maestra anidada debe agregar sus propios ContentPlaceHolders a los controles Content.

The Top-Level and Nested Master Pages Dictate the Content Page's Layout

Ilustración 05: las páginas maestras anidadas y de nivel superior dictan el diseño de la página de contenido (haga clic para ver la imagen a tamaño completo)

Este comportamiento muestra cómo una página de contenido o una página maestra solo conoce su página maestra principal. Este comportamiento también se indica mediante el Diseñador de Visual Studio. En la ilustración 6 se muestra el Diseñador para Default.aspx. Aunque el Diseñador muestra claramente qué regiones se pueden editar desde la página de contenido y qué partes no, no aclara qué regiones no editables proceden de la página maestra anidada y qué regiones proceden de la página maestra de nivel superior.

The Content Page Now Includes Content Controls for the Nested Master Page's ContentPlaceHolders

Ilustración 06: la página Content ahora incluye controles Content para los ContentPlaceHolders de la página maestra anidada (haga clic para ver la imagen a tamaño completo)

Paso 3: adición de una segunda página maestra anidada simple

La ventaja de las páginas maestras anidadas es más evidente cuando hay varias páginas maestras anidadas. Para ilustrar esta ventaja, cree otra página maestra anidada en la carpeta NestedMasterPages; asigne un nombre a esta nueva página maestra anidada SimpleNestedAlternate.master y enlácela a la página maestra Simple.master. Agregue controles ContentPlaceHolder en los dos controles Content de la página maestra anidada, como hicimos en el paso 2. Agregue también el texto "Hello, from SimpleNestedAlternate!" en el control Content que corresponde al ContentPlaceHolder de la página maestra MainContent de nivel superior. Después de realizar estos cambios, el nuevo marcado declarativo de la página maestra anidada debe ser similar al siguiente:

<%@ Master Language="C#" MasterPageFile="~/NestedMasterPages/Simple.master" AutoEventWireup="false" CodeFile="SimpleNestedAlternate.master.cs" Inherits="NestedMasterPages_SimpleNestedAlternate" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server">
 <asp:ContentPlaceHolder ID="head" runat="server">
 </asp:ContentPlaceHolder> 
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server">
 <p>Hello, from SimpleNestedAlternate!</p> 
 <asp:ContentPlaceHolder ID="MainContent" runat="server">
 </asp:ContentPlaceHolder> 
 </asp:Content>

Cree una página de contenido denominada Alternate.aspx en la carpeta NestedMasterPages y enlácela a la página maestra anidada SimpleNestedAlternate.master. Agregue el texto "Hello, from Alternate!" en el control Content que corresponde a MainContent. En la ilustración 7 se muestra Alternate.aspx cuando se ve a través del Diseñador de Visual Studio.

Alternate.aspx is Bound to the SimpleNestedAlternate.master Master Page

Ilustración 07: Alternate.aspx está enlazado a la página maestra SimpleNestedAlternate.master (haga clic para ver la imagen a tamaño completo)

Compare el Diseñador en la ilustración 7 con el Diseñador en la ilustración 6. Ambas páginas de contenido comparten el mismo diseño definido en la página maestra de nivel superior (Simple.master), es decir, el título "Tutorial de páginas maestras anidadas (simple)". Sin embargo, ambos tienen contenido distinto definido en sus páginas maestras principales: el texto "Hello, from SimpleNested!" en la ilustración 6 y "Hello, from SimpleNestedAlternate!" en la ilustración 7. Es cierto que estas diferencias son triviales, pero se podría extender el ejemplo para incluir otras más significativas. Por ejemplo, la página SimpleNested.master puede incluir un menú con opciones específicas de sus páginas de contenido, mientras que SimpleNestedAlternate.master podría tener información pertinente para las páginas de contenido que se enlazan a ella.

Ahora, imagine que necesitamos realizar un cambio en el diseño de sitio general. Por ejemplo, imagine que queríamos agregar una lista de vínculos comunes a todas las páginas de contenido. Para ello, actualizamos la página maestra de nivel superior, Simple.master. Cualquier cambio se refleja inmediatamente en sus páginas maestras anidadas y, por extensión, en sus páginas de contenido.

Para mostrar la facilidad con la que podemos cambiar el diseño del sitio general, abra la página maestra Simple.master y agregue el siguiente marcado entre los elementos topContent, mainContent y <div>:

<div id="navContent"> 
 <asp:HyperLink ID="lnkDefault" runat="server" 
 NavigateUrl="~/NestedMasterPages/Default.aspx" 
 Text="Nested Master Page Example 1" /> 
 | 
 <asp:HyperLink ID="lnkAlternate" runat="server" 
 NavigateUrl="~/NestedMasterPages/Alternate.aspx" 
 Text="Nested Master Page Example 2" /> 
</div>

Esto agrega dos vínculos a la parte superior de cada página que enlaza a Simple.master, SimpleNested.master o SimpleNestedAlternate.master; estos cambios se aplican a todas las páginas maestras anidadas y sus páginas de contenido inmediatamente. En la Ilustración 8 se muestra Alternate.aspx cuando se ve a través de un explorador. Observe la adición de los vínculos en la parte superior de la página (en comparación con la ilustración 7).

Changed to the Top-Level Master Page are Immediately Reflected in its Nested Master Pages and Their Content Pages

Ilustración 08: los cambios en la página maestra de nivel superior se reflejan inmediatamente en sus páginas maestras anidadas y en sus páginas de contenido (haga clic para ver la imagen a tamaño completo)

Uso de una página maestra anidada para la sección Administración

Hasta aquí hemos visto las ventajas de las páginas maestras anidadas y hemos visto cómo crearlas y usarlas en una aplicación de ASP.NET. Sin embargo, los ejemplos de los pasos 1, 2 y 3 implican la creación de una nueva página maestra de nivel superior, nuevas páginas maestras anidadas y páginas de contenido nuevas. ¿Qué pasa con la adición de una nueva página maestra anidada a un sitio web con una página maestra de nivel superior y páginas de contenido existentes?

Integrar una página maestra anidada en un sitio web existente y asociarla a páginas de contenido existentes requiere un poco más de esfuerzo que empezar desde cero. Los pasos 4, 5, 6 y 7 exploran estos desafíos a medida que aumentamos nuestra aplicación de demostración para incluir una nueva página maestra anidada denominada AdminNested.master que contiene instrucciones para el administrador y la usan las páginas de ASP.NET de la carpeta ~/Admin.

La integración de una página maestra anidada en nuestra aplicación de demostración presenta los siguientes desafíos:

  • Las páginas de contenido existentes de la carpeta ~/Admin tienen ciertas expectativas de su página maestra. Para empezar, esperan que determinados controles ContentPlaceHolder estén presentes. Además, las páginas ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx llaman al método público RefreshRecentProductsGrid de la página maestra, establecen su propiedad GridMessageText o tienen un controlador de eventos para su evento PricesDoubled. Por lo tanto, nuestra página maestra anidada debe proporcionar los mismos ContentPlaceHolders y miembros públicos.
  • En el tutorial anterior hemos mejorado la clase BasePage para establecer dinámicamente la propiedad Page del objeto MasterPageFile en función de una variable Session. ¿Cómo se admiten las páginas maestras dinámicas al usar páginas maestras anidadas?

Estos dos desafíos se mostrarán a medida que compilamos la página maestra anidada y la usamos desde nuestras páginas de contenido existentes. Investigaremos y superaremos estos problemas a medida que surjan.

Paso 4: creación de la página maestra anidada

Nuestra primera tarea consiste en crear la página maestra anidada que usarán las páginas de la sección Administración. Como vimos en el paso 2, al agregar una nueva página maestra anidada, es necesario especificar la página maestra principal de la página maestra anidada. Pero tenemos dos páginas maestras de nivel superior: Site.master y Alternate.master. Recuerde que hemos creado Alternate.master en el tutorial anterior y hemos escrito código en la clase BasePage que establece la propiedad del objeto MasterPageFile en runtime en Site.master o Alternate.master en función del valor de la variable Session MyMasterPage.

¿Cómo configuramos nuestra página maestra anidada para que use la página maestra de nivel superior adecuada? Tenemos dos opciones:

  • Cree dos páginas maestras anidadas, AdminNestedSite.master y AdminNestedAlternate.master, y enlácelas a las páginas maestras de nivel superior Site.master y Alternate.master, respectivamente. En BasePage, después, estableceríamos el Page del objeto MasterPageFile en la página maestra anidada adecuada.
  • Cree una sola página maestra anidada y haga que las páginas de contenido usen esta página maestra determinada. A continuación, en runtime, tendríamos que establecer la propiedad MasterPageFile de la página maestra anidada en la página maestra de nivel superior adecuada en runtime. (Como podría haber descubierto ahora, las páginas maestras también tienen una propiedad MasterPageFile).

Vamos a usar la segunda opción. Cree un único archivo de página maestra anidada en la carpeta ~/Admin denominada AdminNested.master. Dado que tanto Site.master como Alternate.master tienen el mismo conjunto de controles ContentPlaceHolder, no importa la página maestra a la que se enlace, aunque le animamos a enlazarla a Site.master por motivos de coherencia.

Add a Nested Master Page to the ~/Admin Folder.

Ilustración 09: adición de una página maestra anidada a la carpeta ~/Admin. (Haga clic para ver la imagen a tamaño completo.)

Dado que la página maestra anidada está enlazada a una página maestra con cuatro controles ContentPlaceHolder, Visual Studio agrega cuatro controles Content al nuevo marcado inicial del archivo de página maestra anidada. Igual que en los pasos 2 y 3, agregue un control ContentPlaceHolder en cada control Content, dándole el mismo nombre que el control ContentPlaceHolder de la página maestra de nivel superior. Agregue también el siguiente marcado al control Content que corresponde al ContentPlaceHolder MainContent:

<div class="instructions"> 
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
</div>

A continuación, defina la clase CSS instructions en los archivos CSS Styles.css y AlternateStyles.css. Las siguientes reglas CSS hacen que los elementos HTML con estilo con la clase instructions se muestren con un color de fondo amarillo claro y un borde negro sólido:

.instructions 
{ 
 padding: 6px; 
 border: dashed 1px black; 
 background-color: #ffb; 
 margin-bottom: 10px; 
}

Dado que este marcado se ha agregado a la página maestra anidada, solo aparecerá en las páginas que usan esta página maestra anidada (es decir, las páginas de la sección Administración).

Después de realizar estas adiciones a la página maestra anidada, su marcado declarativo debe ser similar al siguiente:

<%@ Master Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="false" CodeFile="AdminNested.master.cs" Inherits="Admin_AdminNested" %> 
 <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> 
 <asp:ContentPlaceHolder ID="head" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" Runat="Server"> 
 <div class="instructions">
 <b>Administration Instructions:</b>
 <br /> 
 The pages in the Administration section allow you, the Administrator, to 
 add new products and view existing products. 
 </div> 
 <asp:ContentPlaceHolder ID="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content3" ContentPlaceHolderID="QuickLoginUI" Runat="Server"> 
 <asp:ContentPlaceHolder ID="QuickLoginUI" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content> 
 <asp:Content ID="Content4" ContentPlaceHolderID="LeftColumnContent" Runat="Server"> 
 <asp:ContentPlaceHolder ID="LeftColumnContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </asp:Content>

Tenga en cuenta que cada control Content tiene un control ContentPlaceHolder y que a las propiedades ID de los controles ContentPlaceHolder se les asignan los mismos valores que los controles ContentPlaceHolder correspondientes en la página maestra de nivel superior. Además, el marcado específico de la sección Administración aparece en el ContentPlaceHolder MainContent.

En la ilustración 10 se muestra la página maestra anidada AdminNested.master cuando se ve a través del Diseñador de Visual Studio. Puede ver las instrucciones del cuadro amarillo en la parte superior del control Content MainContent.

The Nested Master Page Extends the Top-Level Master Page to Include Instructions for the Administrator.

Ilustración 10: la página maestra anidada extiende la página maestra de nivel superior para incluir instrucciones para el administrador. (Haga clic para ver la imagen a tamaño completo.)

Paso 5: actualización de las páginas de contenido existentes para usar la nueva página maestra anidada

Cada vez que agreguemos una nueva página de contenido a la sección Administración, es necesario enlazarla a la página maestra AdminNested.master que acabamos de crear. ¿Pero qué ocurre con las páginas de contenido existentes? Actualmente, todas las páginas de contenido del sitio derivan de la clase BasePage, que establece mediante programación la página maestra de la página de contenido en runtime. Este no es el comportamiento que queremos para las páginas de contenido de la sección Administración. En su lugar, queremos que estas páginas de contenido usen siempre la página AdminNested.master. Será responsabilidad de la página maestra anidada elegir la página de contenido de nivel superior correcta en runtime.

Para lograr este comportamiento deseado, debemos crear una nueva clase de página base personalizada denominada AdminBasePage que extienda la clase BasePage. AdminBasePage puede invalidar el SetMasterPageFile y establecer el MasterPageFile del objeto Page en el valor codificado de forma rígida "~/Admin/AdminNested.master". De este modo, cualquier página que derive de AdminBasePage usará AdminNested.master, mientras que cualquier página que derive de BasePage tendrá su propiedad MasterPageFile establecida dinámicamente en "~/Site.master" o "~/Alternate.master" en función del valor de la variable Session MyMasterPage.

Empiece agregando un nuevo archivo de clase a la carpeta App_Code denominada AdminBasePage.cs. Haga que AdminBasePage extienda BasePage y, a continuación, invalide el método SetMasterPageFile. En ese método, asigne al MasterPageFile el valor "~/Admin/AdminNested.master". Después de realizar estos cambios, el archivo de clase debe ser similar al siguiente:

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
    } 
}

Ahora es necesario que las páginas de contenido existentes de la sección Administración deriven de AdminBasePage en lugar de BasePage. Vaya al archivo de clase de código subyacente para cada página de contenido de la carpeta ~/Admin y realice este cambio. Por ejemplo, en ~/Admin/Default.aspx cambiaría la declaración de clase de código subyacente de:

public partial class Admin_Default : BasePage

A:

public partial class Admin_Default : AdminBasePage

En la ilustración 11 se muestra cómo la página maestra de nivel superior (Site.master o Alternate.master), la página maestra anidada (AdminNested.master) y las páginas de contenido de la sección Administración se relacionan entre sí.

The Nested Master Page Defines Content Specific to the Pages in the Administration Section

Ilustración 11: la página maestra anidada define contenido específico de las páginas de la sección Administración (haga clic para ver la imagen a tamaño completo)

Paso 6: creación de reflejo de los métodos y propiedades públicos de la página maestra

Recuerde que las páginas ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx interactúan mediante programación con la página maestra: ~/Admin/AddProduct.aspx llama al método público RefreshRecentProductsGrid de la página maestra y establece su propiedad GridMessageText; ~/Admin/Products.aspx tiene un controlador de eventos para el evento PricesDoubled. En el tutorial anterior creamos una clase abstracta BaseMasterPage que definió estos miembros públicos.

Las páginas ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx asumen que su página maestra deriva de la clase BaseMasterPage. Sin embargo, la página AdminNested.master amplía actualmente la clase System.Web.UI.MasterPage. Como resultado, al visitar ~/Admin/Products.aspx se produce un InvalidCastException con el mensaje : "No se puede convertir el objeto de tipo "ASP.admin_adminnested_master" al tipo "BaseMasterPage".

Para corregir esto, es necesario que la clase de código subyacente AdminNested.master extienda BaseMasterPage. Actualice la declaración de clase de código subyacente de la página maestra anidada de:

public partial class Admin_AdminNested : System.Web.UI.MasterPage

A:

public partial class Admin_AdminNested : BaseMasterPage

Aún no hemos terminado. Dado que la BaseMasterPage clase es abstracta, es necesario invalidar los abstract miembros RefreshRecentProductsGrid y GridMessageText. Estas páginas maestras de nivel superior usan estos miembros para actualizar sus interfaces de usuario. (En realidad, solo la página maestra Site.master usa estos métodos, aunque ambas páginas maestras de nivel superior implementan estos métodos, ya que ambas extiendan BaseMasterPage).

Aunque es necesario implementar estos miembros en AdminNested.master, lo único que deben hacer todas estas implementaciones es simplemente llamar al mismo miembro en la página maestra de nivel superior usada por la página maestra anidada. Por ejemplo, cuando una página de contenido de la sección Administración llama al método RefreshRecentProductsGrid de la página maestra anidada, toda la página maestra anidada debe, a su vez, llamar al método Site.master o RefreshRecentProductsGrid de Alternate.master.

Para ello, empiece agregando la siguiente directiva @MasterType a la parte superior de AdminNested.master:

<%@ MasterType TypeName="BaseMasterPage" %>

Recuerde que la directiva @MasterType agrega una propiedad fuertemente tipada a la clase de código subyacente denominada Master. A continuación, invalide los miembros RefreshRecentProductsGrid y GridMessageText y simplemente delegue la llamada al método Master correspondiente:

public partial class Admin_AdminNested : BaseMasterPage 
{ 
    public override void RefreshRecentProductsGrid() 
    { 
        Master.RefreshRecentProductsGrid();
    } 
    public override string GridMessageText
    { 
        get 
        {
            return Master.GridMessageText;
        } 
        set
        { 
            Master.GridMessageText = value; 
        } 
    }
}

Con este código en su lugar, debería poder visitar y usar las páginas de contenido de la sección Administración. En la ilustración 12 se muestra la página ~/Admin/Products.aspx cuando se ve a través de un explorador. Como puede ver, la página incluye el cuadro Instrucciones de administración, que se define en la página maestra anidada.

The Content Pages in the Administration Section Include Instructions at the Top of Each Page

Ilustración 12: las páginas de contenido de la sección Administración incluyen instrucciones en la parte superior de cada página (haga clic para ver la imagen a tamaño completo)

Paso 7: uso de la página maestra de nivel superior adecuada en runtime

Aunque todas las páginas de contenido de la sección Administración son totalmente funcionales, todas usan la misma página maestra de nivel superior y omiten la página maestra seleccionada por el usuario en ChooseMasterPage.aspx. Este comportamiento se debe al hecho de que la página maestra anidada tiene su propiedad MasterPageFile establecida estáticamente en Site.master en su directiva <%@ Master %>.

Para usar la página maestra de nivel superior seleccionada por el usuario final, es necesario establecer la propiedad MasterPageFile de AdminNested.master en el valor de la variable Session MyMasterPage. Dado que establecemos las propiedades MasterPageFile de las páginas de contenido en BasePage, puede pensar que estableceríamos la propiedad MasterPageFile de la página maestra anidada en BaseMasterPage o en la clase de código subyacente de AdminNested.master. Sin embargo, esto no funcionará, ya que es necesario establecer la propiedad MasterPageFile al final de la fase PreInit. El primer momento en el que podemos acceder mediante programación al ciclo de vida de la página desde una página maestra es la etapa Init (que se produce después de la etapa PreInit).

Por lo tanto, es necesario establecer la propiedad MasterPageFile de la página maestra anidada de las páginas de contenido. Las únicas páginas de contenido que usan la página maestra AdminNested.master derivan de AdminBasePage. Por lo tanto, podemos poner esta lógica ahí. En el paso 5, sobrescribimos el método SetMasterPageFile, estableciendo la propiedad Page del objeto MasterPageFile en "~/Admin/AdminNested.master". Actualice SetMasterPageFile para establecer también la propiedad MasterPageFile de la página maestra en el resultado almacenado en Session:

public class AdminBasePage : BasePage 
{ 
    protected override void SetMasterPageFile() 
    { 
        this.MasterPageFile = "~/Admin/AdminNested.master"; 
        Page.Master.MasterPageFile = base.GetMasterPageFileFromSession(); 
    } 
}

El método GetMasterPageFileFromSession, que agregamos a la clase BasePage del tutorial anterior, devuelve la ruta de acceso del archivo de página maestra adecuada en función del valor de la variable Session.

Con este cambio en su lugar, la selección de página maestra del usuario se lleva a la sección Administración. La ilustración 13 muestra la misma página que la ilustración 12, pero después de que el usuario haya cambiado su selección de página maestra a Alternate.master.

The Nested Administration Page Uses the Top-Level Master Page Selected by the User

Ilustración 13: la página Administración anidada usa la página maestra de nivel superior seleccionada por el usuario (haga clic para ver la imagen a tamaño completo)

Resumen

Al igual que la forma en que las páginas de contenido se pueden enlazar a una página maestra, es posible crear páginas maestras anidadas haciendo que una página maestra secundaria se enlace a una página maestra primaria. La página maestra secundaria puede definir controles Content para cada uno de los ContentPlaceHolders de su elemento primario; después, puede agregar sus propios controles ContentPlaceHolder (así como otro marcado) a estos controles Content. Las páginas maestras anidadas son bastante útiles en aplicaciones web grandes en las que todas las páginas comparten una apariencia general, pero algunas secciones del sitio requieren personalizaciones únicas.

¡Feliz programación!

Lecturas adicionales

Para obtener más información sobre los temas tratados en este tutorial, consulte los siguientes recursos:

Acerca del autor

Scott Mitchell, autor de varios libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 3.5 in 24 Hours. Se puede contactar con Scott en mitchell@4GuysFromRolla.com o a través de su blog en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores que fueron de gran ayuda. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com