Especificar la página maestra mediante programación (VB)

por Scott Mitchell

Examina el establecimiento de la página maestra de la página de contenido mediante programación a través del controlador de eventos PreInit.

Introducción

Desde el ejemplo inicial en Crear un diseño de todo el sitio mediante páginas maestras, todas las páginas de contenido han hecho referencia a su página maestra de forma declarativa a través del atributo MasterPageFile de la directiva @Page. Por ejemplo, la siguiente directiva @Page vincula la página de contenido a la página maestra Site.master:

<%@ Page Language="C#" MasterPageFile="~/Site.master"... %>

La clase Page del espacio de nombres System.Web.UI incluye una propiedad MasterPageFile que devuelve la ruta de acceso a la página maestra de la página de contenido; es esta propiedad la que establece la directiva @Page. Esta propiedad también se puede usar para especificar mediante programación la página maestra de la página de contenido. Este enfoque es útil si desea asignar dinámicamente la página maestra en función de factores externos, como el usuario que visita la página.

En este tutorial, agregamos una segunda página maestra a nuestro sitio web y decidimos dinámicamente qué página maestra usar en tiempo de ejecución.

Paso 1: Un vistazo al ciclo de vida de la página

Cada vez que llega una solicitud al servidor web para una página de ASP.NET que es una página de contenido, el motor de ASP.NET debe fusionar los controles de contenido de la página en los controles ContentPlaceHolder correspondientes de la página maestra. Esta fusión crea una única jerarquía de controles que puede seguir el ciclo de vida típico de las páginas.

En la figura 1 se ilustra esta fusión. En el paso 1 de la figura 1 se muestra el contenido inicial y las jerarquías de control de la página maestra. Al final de la fase PreInit, los controles de contenido de la página se agregan a los ContentPlaceHolders correspondientes en la página maestra (paso 2). Después de esta fusión, la página maestra actúa como la raíz de la jerarquía de control fusionada. A continuación, esta jerarquía de control fusionada se agrega a la página para generar la jerarquía de control finalizada (paso 3). El resultado neto es que la jerarquía de controles de la página incluye la jerarquía de control fusionada.

The Master Page and Content Page's Control Hierarchies are Fused Together during the PreInit Stage

Figura 01: Las jerarquías de control de la página maestra y la página de contenido se fusionan durante la fase de PreInit (haga clic para ver la imagen de tamaño completo)

Paso 2: Establecer laMasterPageFilepropiedad desde el código

Qué parte de la página maestra participa en esta fusión depende del valor de la propiedad MasterPageFile del objeto Page. Establecer el atributo MasterPageFile en la directiva @Page tiene el efecto neto de asignar la propiedad MasterPageFile de Page durante la fase de inicialización, que es la primera fase del ciclo de vida de la página. También podemos establecer esta propiedad mediante programación. Sin embargo, es imperativo establecer esta propiedad antes de que se produzca la fusión en la figura 1.

Al principio de la fase PreInit, el objeto Page genera su evento PreInit y llama a su método OnPreInit. Para establecer la página maestra mediante programación, podemos crear un controlador de eventos para el evento PreInit o reemplazar el método OnPreInit. Veamos ambos métodos.

Comience abriendo Default.aspx.vb, el archivo de clase de código subyacente para la página principal de nuestro sitio. Cree un controlador de eventos para el evento PreInit de la página escribiendo el siguiente código:

Protected Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreInit 
End Sub

Desde aquí podemos establecer la propiedad MasterPageFile. Actualice el código para que asigne el valor "~/Site.master" a la propiedad MasterPageFile.

Protected Sub Page_PreInit(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreInit 
 Me.MasterPageFile = "~/Site.master"
End Sub

Si establece un punto de interrupción y comienza con la depuración, verá que cada vez que se visita la página Default.aspx o cada vez que hay un postback a esta página, se ejecuta el controlador de eventos Page_PreInit y la propiedad MasterPageFile se asigna a "~/Site.master".

Como alternativa, puede invalidar el método OnPreInit de la clase Page y establecer la propiedad MasterPageFile allí. En este ejemplo, no estableceremos la página maestra en una página determinada, sino desde BasePage. Recuerde que hemos creado una clase de página base personalizada (BasePage) en el tutorial Especificar el título, las etiquetas meta y otros encabezados HTML en el tutorial de página maestra. Actualmente BasePage invalida el método OnLoadComplete de la clase Page, donde establece la propiedad Title de la página en función de los datos del mapa del sitio. Vamos a actualizar BasePage para invalidar también el método OnPreInit para especificar mediante programación la página maestra.

Protected Overrides Sub OnPreInit(ByVal e As System.EventArgs)
 Me.MasterPageFile = "~/Site.master" 
 MyBase.OnPreInit(e)
End Sub

Dado que todas nuestras páginas de contenido derivan de BasePage, todas ellas ahora tienen asignada su página maestra mediante programación. En este momento, el controlador de eventos PreInit de Default.aspx.vb es superfluo; no dude en quitarlo.

¿Qué ocurre con la directiva @Page?

Lo que puede resultar un poco confuso es que las propiedades MasterPageFile de las páginas de contenido ahora se especifican en dos lugares: mediante programación en el método OnPreInit de la clase BasePage, así como a través del atributo MasterPageFile en la directiva @Page de cada página de contenido.

La primera fase del ciclo de vida de la página es la fase de inicialización. Durante esta fase, a la propiedad MasterPageFile del objeto Page se le asigna el valor del atributo MasterPageFile en la directiva @Page (si se proporciona). La fase PreInit sigue la fase de inicialización y es aquí donde establecemos mediante programación la propiedad MasterPageFile del objeto Page, lo que sobrescribe el valor asignado de la directiva @Page. Dado que estamos estableciendo la propiedad MasterPageFile del objeto Page mediante programación, podríamos quitar el atributo MasterPageFile de la directiva @Page sin afectar a la experiencia del usuario final. Para convencerse de esto, continúe y quite el atributo MasterPageFile de la directiva @Page en Default.aspx y, a continuación, visite la página a través de un explorador. Como cabría esperar, la salida es la misma que antes de quitar el atributo.

Si la propiedad MasterPageFile se establece a través de la directiva @Page o mediante programación no afecta a la experiencia del usuario final. Sin embargo, Visual Studio usa el atributo MasterPageFile de la directiva @Page durante el diseño para generar la vista WYSIWYG en el Diseñador. Si vuelve a Default.aspx en Visual Studio y navega al Diseñador, verá el mensaje "Master Page error: The page has controls that require a Master Page reference, but none is specified" (Error de página maestra: la página tiene controles que requieren una referencia de página maestra, pero no se especifica ninguno) (vea la figura 2).

En resumen, es necesario dejar el atributo MasterPageFile en la directiva @Page para disfrutar de una rica experiencia en tiempo de diseño en Visual Studio.

Visual Studio Uses the <span class=Atributo MasterPageFile de la directiva @Page para representar la vista de diseño" />

Figura 02: Visual Studio usa el atributo MasterPageFile de la directiva @Page para representar la vista diseño (haga clic para ver la imagen de tamaño completo)

Paso 3: Crear una página maestra alternativa

Dado que la página maestra de una página de contenido se puede establecer mediante programación en tiempo de ejecución, es posible cargar dinámicamente una página maestra determinada basada en algunos criterios externos. Esta funcionalidad puede ser útil en situaciones en las que el diseño del sitio debe variar en función del usuario. Por ejemplo, una aplicación web del motor de blog puede permitir a sus usuarios elegir un diseño para su blog, donde cada diseño está asociado a una página maestra diferente. En tiempo de ejecución, cuando un visitante está viendo el blog de un usuario, la aplicación web tendría que determinar el diseño del blog y asociar dinámicamente la página maestra correspondiente a la página de contenido.

Vamos a ver cómo se carga dinámicamente una página maestra en tiempo de ejecución en función de algunos criterios externos. Nuestro sitio web contiene actualmente solo una página maestra (Site.master). Necesitamos otra página maestra para ilustrar la elección de una página maestra en tiempo de ejecución. Este paso se centra en la creación y configuración de la nueva página maestra. En el paso 4 se examina la determinación de la página maestra que se va a usar en tiempo de ejecución.

Cree una nueva página maestra en la carpeta raíz denominada Alternate.master. Agregue también una nueva hoja de estilos al sitio web denominado AlternateStyles.css.

Add Another Master Page and CSS File to the Website

Figura 03: Agregar otra página maestra y archivo CSS al sitio web (haga clic para ver la imagen de tamaño completo)

He diseñado la página maestra Alternate.master para que el título se muestre en la parte superior de la página, centrado y sobre un fondo azul marino. He dispensado la columna izquierda y he movido ese contenido debajo del control ContentPlaceHolder MainContent, que ahora abarca todo el ancho de la página. Además, rechacé la lista de lecciones no ordenadas y la reemplacé por una lista horizontal encima de MainContent. También actualicé las fuentes y colores utilizados por la página maestra (y, por extensión, sus páginas de contenido). En la figura 4 se muestra Default.aspx cuando se utiliza la página maestra Alternate.master.

Nota:

ASP.NET incluye la capacidad de definir temas. Un tema es una colección de imágenes, archivos CSS y configuraciones de propiedades de control web relacionadas con el estilo que se pueden aplicar a una página en tiempo de ejecución. Los temas son el camino a seguir si los diseños del sitio solo difieren en las imágenes mostradas y por sus reglas CSS. Si los diseños difieren de forma más sustancial, como por ejemplo si se utilizan diferentes controles web o tienen un diseño radicalmente diferente, deberá usar páginas maestras independientes. Para obtener más información sobre los temas, consulte la sección Información adicional al final de este tutorial.

Our Content Pages Can Now Use a New Look and Feel

Figura 04: Nuestras páginas de contenido ahora pueden usar una nueva apariencia (hacer clic para ver la imagen de tamaño completo)

Cuando se fusiona el marcado de las páginas maestras y de contenido, la clase MasterPage comprueba que cada control de contenido de la página de contenido haga referencia a un ContentPlaceHolder en la página maestra. Se produce una excepción si se encuentra un control de contenido que hace referencia a un ContentPlaceHolder no existente. En otras palabras, es imperativo que la página maestra que se asigna a la página de contenido tenga un ContentPlaceHolder para cada control de contenido de la página de contenido.

La página maestra Site.master incluye cuatro controles ContentPlaceHolder:

  • head
  • MainContent
  • QuickLoginUI
  • LeftColumnContent

Algunas de las páginas de contenido de nuestro sitio web incluyen solo uno o dos controles de contenido; otras incluyen un control de contenido para cada uno de los ContentPlaceHolders disponibles. Si nuestra nueva página maestra (Alternate.master) se puede asignar alguna vez a esas páginas de contenido que tienen controles de contenido para todos los ContentPlaceHolders en Site.master, es fundamental que Alternate.master también incluya los mismos controles ContentPlaceHolder que Site.master.

Para que la página maestra Alternate.master tenga un aspecto similar a la mía (vea la figura 4), empiece por definir los estilos de la página maestra en la hoja de estilos AlternateStyles.css. Añada las siguientes reglas a AlternateStyles.css:

body 
{
 font-family: Comic Sans MS, Arial; 
 font-size: medium; 
 margin: 0px; 
} 
#topContent 
{ 
 text-align: center; 
 background-color: Navy; 
 color: White; 
 font-size: x-large;
 text-decoration: none; 
 font-weight: bold; 
 padding: 10px; 
 height: 50px;
} 
#topContent a 
{ 
 text-decoration: none; 
 color: White; 
} 
#navContent 
{ 
 font-size: small; 
 text-align: center; 
} 
#footerContent 
{ 
 padding: 10px; 
 font-size: 90%; 
 text-align: center; 
 border-top: solid 1px black; 
} 
#mainContent 
{ 
 text-align: left; 
 padding: 10px;
}

A continuación, agregue el siguiente marcado declarativo a Alternate.master. Como puede ver, Alternate.master contiene cuatro controles ContentPlaceHolder con los mismos valores ID que los controles ContentPlaceHolder en Site.master. Además, incluye un control ScriptManager, que es necesario para aquellas páginas de nuestro sitio web que utilicen el marco ASP.NET AJAX.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head id="Head1" runat="server"> 
 <title>Untitled Page</title>
 <asp:ContentPlaceHolder id="head" runat="server">
 </asp:ContentPlaceHolder> 
 <link href="AlternateStyles.css" rel="stylesheet" type="text/css"/> 
</head> 
<body> 
 <form id="form1" runat="server"> 
 <asp:ScriptManager ID="MyManager" runat="server"> 
 </asp:ScriptManager>
 <div id="topContent">
 <asp:HyperLink ID="lnkHome" runat="server" NavigateUrl="~/Default.aspx" 
 Text="Master Pages Tutorials" /> 
 </div>
 <div id="navContent"> 
 <asp:ListView ID="LessonsList" runat="server" 
 DataSourceID="LessonsDataSource">
 <LayoutTemplate>
 <asp:PlaceHolder runat="server" ID="itemPlaceholder" /> 
 </LayoutTemplate>
 <ItemTemplate>
 <asp:HyperLink runat="server" ID="lnkLesson" 
 NavigateUrl='<%# Eval("Url") %>' 
 Text='<%# Eval("Title") %>' /> 
 </ItemTemplate>
 <ItemSeparatorTemplate> | </ItemSeparatorTemplate> 
 </asp:ListView>
 <asp:SiteMapDataSource ID="LessonsDataSource" runat="server" 
 ShowStartingNode="false" /> 
 </div> 
 <div id="mainContent">
 <asp:ContentPlaceHolder id="MainContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </div>
 <div id="footerContent">
 <p> 
 <asp:Label ID="DateDisplay" runat="server"></asp:Label> 
 </p>
 <asp:ContentPlaceHolder ID="QuickLoginUI" runat="server"> 
 </asp:ContentPlaceHolder>
 <asp:ContentPlaceHolder ID="LeftColumnContent" runat="server"> 
 </asp:ContentPlaceHolder>
 </div> 
 </form>
</body> 
</html>

Prueba de la nueva página maestra

Para probar esta nueva página maestra, actualice el método OnPreInit de la clase BasePage para que se asigne el valor "~/Alternate.maser" a la propiedad MasterPageFile y, a continuación, visite el sitio web. Todas las páginas deben funcionar sin errores excepto dos: ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx. Agregar un producto a DetailsView en ~/Admin/AddProduct.aspx da como resultado una NullReferenceException desde la línea de código que intenta establecer la propiedad GridMessageText de la página maestra. Al visitar ~/Admin/Products.aspx, se lanza una InvalidCastException en la carga de página con el mensaje: "Unable to cast object of type 'ASP.alternate_master' to type 'ASP.site_master'" (No se puede convertir el objeto de tipo 'ASP.alternate_master' al tipo 'ASP.site_master'.

Estos errores se producen porque la clase de código subyacente Site.master incluye eventos públicos, propiedades y métodos que no están definidos en Alternate.master. La parte de marcado de estas dos páginas tiene una directiva @MasterType que hace referencia a la página maestra Site.master.

<%@ MasterType VirtualPath="~/Site.master" %>

Además, el controlador de eventos ItemInserted de DetailsView de ~/Admin/AddProduct.aspx incluye código que convierte la propiedad Page.Master de tipo flexible en un objeto de tipo Site. La directiva @MasterType (se usa de esta manera) y la conversión en el controlador de eventos ItemInserted acopla estrechamente las páginas ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx a la página maestra Site.master.

Para romper este estrecho acoplamiento podemos hacer que Site.master y Alternate.master deriven de una clase base común que contenga definiciones para los miembros públicos. Después, podemos actualizar la directiva @MasterType para hacer referencia a este tipo base común.

Crear una clase de página maestra base personalizada

Agregue un nuevo archivo de clase a la carpeta App_Code denominada BaseMasterPage.vb y haga que se derive de System.Web.UI.MasterPage. Es necesario definir el método RefreshRecentProductsGrid y la propiedad GridMessageText en BaseMasterPage, pero no podemos simplemente moverlos allí desde Site.master porque estos miembros trabajan con controles web específicos de la página maestra Site.master (el GridView RecentProducts y la etiqueta GridMessage).

Lo que debemos hacer es configurar BaseMasterPage de tal manera que estos miembros se definan allí, pero en realidad se implementan mediante las clases derivadas de BaseMasterPage (Site.master y Alternate.master). Este tipo de herencia es posible marcando la clase como MustInherit y sus miembros como MustOverride. En resumen, agregar estas palabras clave a la clase y a sus dos miembros anuncia que BaseMasterPage no ha implementado RefreshRecentProductsGrid y GridMessageText, pero que sus clases derivadas lo harán.

También es necesario definir el evento PricesDoubled en BaseMasterPage y proporcionar un medio por las clases derivadas para generar el evento. El patrón usado en .NET Framework para facilitar este comportamiento es crear un evento público en la clase base y agregar un método protegido y reemplazable denominado OnEventName. A continuación, las clases derivadas pueden llamar a este método para generar el evento o invalidarlo para ejecutar código inmediatamente antes o después de que se genere el evento.

Actualice la clase BaseMasterPage para que contenga el código siguiente:

Public MustInherit Class BaseMasterPage 
 Inherits System.Web.UI.MasterPage 
 Public Event PricesDoubled As EventHandler
 Protected Overridable Sub OnPricesDoubled(ByVal e As EventArgs)
 RaiseEvent PricesDoubled(Me, e)
 End Sub
 Public MustOverride Sub RefreshRecentProductsGrid() 
 Public MustOverride Property GridMessageText() As String 
End Class

A continuación, vaya a la clase de código subyacente Site.master y haga que se derive de BaseMasterPage. Dado que BaseMasterPage contiene miembros marcados MustOverride, necesitamos invalidar esos miembros aquí en Site.master. Agregue la palabra clave Overrides a las definiciones de método y propiedad. Actualice también el código que genera el evento PricesDoubled en el controlador de eventos DoublePrice de Button Click con una llamada al método OnPricesDoubled de la clase base.

Después de estas modificaciones, la clase de código subyacente Site.master debe contener el código siguiente:

Partial Class Site 
 Inherits BaseMasterPage
 Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
 DateDisplay.Text = DateTime.Now.ToString("dddd, MMMM dd")
 End Sub
 Public Overrides Sub RefreshRecentProductsGrid() 
 RecentProducts.DataBind()
 End Sub 
 Public Overrides Property GridMessageText() As String 
 Get
 Return GridMessage.Text
 End Get 
 Set(ByVal Value As String) 
 GridMessage.Text = Value 
 End Set
 End Property 
 Protected Sub DoublePrice_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles DoublePrice.Click 
 ' Double the prices 
 DoublePricesDataSource.Update()
 ' Refresh RecentProducts 
 RecentProducts.DataBind()
 ' Raise the PricesDoubled event
 MyBase.OnPricesDoubled(EventArgs.Empty)
 End Sub 
End Class

También es necesario actualizar la clase de código subyacente de Alternate.master para que se derive de BaseMasterPage e invalidar los dos miembros MustOverride. Pero dado que Alternate.master no contiene un control GridView que enumera los productos más recientes ni una etiqueta que muestra un mensaje después de agregar un nuevo producto a la base de datos, estos métodos no necesitan hacer nada.

Partial Class Alternate 
 Inherits BaseMasterPage
 Public Overrides Property GridMessageText() As String 
 Get
 Return String.Empty
 End Get
 Set(ByVal value As String) 
 ' Do nothing 
 End Set 
 End Property 
 Public Overrides Sub RefreshRecentProductsGrid()
 ' Do nothing 
 End Sub 
End Class

Hacer referencia a la clase de página maestra base

Ahora que hemos completado la clase BaseMasterPage y tenemos nuestras dos páginas maestras que la extienden, nuestro paso final es actualizar las páginas ~/Admin/AddProduct.aspx y ~/Admin/Products.aspx para hacer referencia a este tipo común. Comience cambiando la directiva @MasterType en ambas páginas desde:

<%@ MasterType VirtualPath="~/Site.master" %>

A:

<%@ MasterType TypeName="BaseMasterPage" %>

En lugar de hacer referencia a una ruta de acceso de archivo, la propiedad @MasterType ahora hace referencia al tipo base (BaseMasterPage). Por lo tanto, la propiedad Master fuertemente tipada usada en las clases de código subyacente de ambas páginas es ahora de tipo BaseMasterPage (en lugar de tipo Site). Con este cambio, vuelva a visitar ~/Admin/Products.aspx. Anteriormente, esto produjo un error de conversión porque la página está configurada para usar la página maestra Alternate.master, pero la directiva @MasterType hizo referencia al archivo Site.master. Pero ahora la página se representa sin errores. Esto se debe a que la página maestra Alternate.master se puede convertir a un objeto de tipo BaseMasterPage (ya que lo extiende).

Hay un pequeño cambio que debe realizarse en ~/Admin/AddProduct.aspx. El controlador de eventos ItemInserted del control DetailsView usa tanto la propiedad Master fuertemente tipada como la propiedad Page.Master de tipo flexible. Hemos corregido la referencia fuertemente tipada al actualizar la directiva @MasterType, pero todavía es necesario actualizar la referencia de tipo flexible. Agregue la línea de código siguiente:

Dim myMasterPage As Site = CType(Page.Master, Site)

Con lo siguiente, que convierte Page.Master al tipo base:

Dim myMasterPage As BaseMasterPage = CType(Page.Master, BaseMasterPage)

Paso 4: Determinar qué página maestra enlazar a las páginas de contenido

Nuestra clase BasePage establece actualmente todas las propiedades MasterPageFile de las páginas de contenido en un valor codificado de forma rígida en la fase PreInit del ciclo de vida de la página. Podemos actualizar este código para basar la página maestra en algún factor externo. Quizás la página maestra que se va a cargar depende de las preferencias del usuario que ha iniciado sesión actualmente. En ese caso, tendríamos que escribir código en el método OnPreInit en BasePage que busca las preferencias de la página maestra del usuario que visita actualmente.

Vamos a crear una página web que permita al usuario elegir la página maestra que se va a usar (Site.master o Alternate.master) y guardar esta opción en una variable Session. Empiece por crear una nueva página web en el directorio raíz denominado ChooseMasterPage.aspx. Al crear esta página (o cualquier otra página de contenido a partir de ahora), no es necesario enlazarla a una página maestra porque la página maestra se establece mediante programación en BasePage. Sin embargo, si no enlaza la nueva página a una página maestra, el marcado declarativo predeterminado de la nueva página contiene un formulario web y otro contenido proporcionado por la página maestra. Deberá reemplazar manualmente este marcado por los controles de contenido adecuados. Por ese motivo, me resulta más fácil enlazar la nueva página de ASP.NET a una página maestra.

Nota:

Dado que Site.master y Alternate.master tienen el mismo conjunto de controles ContentPlaceHolder, no importa la página maestra que elija al crear la nueva página de contenido. Por coherencia, sugeriría usar Site.master.

Add a New Content Page to the Website

Figura 05: adición de una nueva página de contenido al sitio web (Haga clic para ver la imagen de tamaño completo)

Actualice el archivo Web.sitemap para incluir una entrada para esta lección. Agregue el siguiente marcado debajo de l<siteMapNode> de la lección Páginas maestras y ASP.NET AJAX:

<siteMapNode url="~/ChooseMasterPage.aspx" title="Choose a Master Page" />

Antes de agregar contenido a la página ChooseMasterPage.aspx, dedique un momento a actualizar la clase de código subyacente de la página para que derive de BasePage (en lugar de System.Web.UI.Page). A continuación, agregue un control DropDownList a la página, establezca su propiedad ID en MasterPageChoice y agregue dos ListItems con los valores Text de "~/Site.master" y "~/Alternate.master".

Agregue un control Button Web a la página y establezca sus propiedades ID y Text en SaveLayout y "Save Layout Choice" (Guardar selección de diseño), respectivamente. En este momento, el marcado declarativo de la página debe tener un aspecto similar al siguiente:

<p> 
 Your layout choice: 
 <asp:DropDownList ID="MasterPageChoice" runat="server"> 
 <asp:ListItem>~/Site.master</asp:ListItem>
 <asp:ListItem>~/Alternate.master</asp:ListItem>
 </asp:DropDownList> 
</p> 
<p> 
 <asp:Button ID="SaveLayout" runat="server" Text="Save Layout Choice" /> 
</p>

Cuando se visita la página por primera vez, es necesario mostrar la opción de página maestra seleccionada actualmente por el usuario. Cree un controlador de eventos Page_Load y agréguele el siguiente código:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
 If Not Page.IsPostBack Then 
 If Session("MyMasterPage") IsNot Nothing Then 
 Dim li As ListItem = MasterPageChoice.Items.FindByText(Session("MyMasterPage").ToString())
 If li IsNot Nothing Then 
 li.Selected = True
 End If 
 End If 
 End If 
End Sub

El código anterior solo se ejecuta en la primera visita de página (y no en postbacks posteriores). Primero comprueba si existe la variable Session MyMasterPage. Si es así, intenta encontrar el ListItem coincidente en el DropDownList MasterPageChoice. Si se encuentra un ListItem coincidente, su propiedad Selected se establece en True.

También necesitamos código que guarde la opción del usuario en la variable Session MyMasterPage. Cree un controlador de eventos para el evento Click de SaveLayout Button y agregue el código siguiente:

Protected Sub SaveLayout_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles SaveLayout.Click 
 Session("MyMasterPage") = MasterPageChoice.SelectedValue 
 Response.Redirect("ChooseMasterPage.aspx")
End Sub

Nota:

Cuando el controlador de eventos Click se ejecuta en postback, ya se ha seleccionado la página maestra. Por lo tanto, la selección de lista desplegable del usuario no entrará en vigor hasta que visite la página siguiente. Response.Redirect obliga al explorador a volver a solicitar ChooseMasterPage.aspx.

Una vez completada la página ChooseMasterPage.aspx, nuestra tarea final es que BasePage asigne la propiedad MasterPageFile en función del valor de la variable Session MyMasterPage. Si la variable Session no está establecida, haga que BasePage sea por defecto Site.master.

Protected Overrides Sub OnPreInit(ByVal e As System.EventArgs)
 SetMasterPageFile() 
 MyBase.OnPreInit(e)
End Sub 
Protected Overridable Sub SetMasterPageFile() 
 Me.MasterPageFile = GetMasterPageFileFromSession() 
End Sub 
Protected Function GetMasterPageFileFromSession() As String 
 If Session("MyMasterPage") Is Nothing Then
 Return "~/Site.master"
 Else 
 Return Session("MyMasterPage").ToString() 
 End If 
End Function

Nota:

He movido el código que asigna la propiedad MasterPageFile del objeto Page fuera del controlador de eventos OnPreInit y en dos métodos independientes. Este primer método, SetMasterPageFile, asigna la propiedad MasterPageFile al valor devuelto por el segundo método, GetMasterPageFileFromSession. He marcado el método SetMasterPageFile como Overridable para que las clases futuras que extiendan BasePage puedan anularlo opcionalmente para implementar una lógica personalizada, si es necesario. Veremos un ejemplo de anulación de la propiedad BasePage de SetMasterPageFile en el siguiente tutorial.

Con este código, visite la página ChooseMasterPage.aspx. Inicialmente, se selecciona la página maestra Site.master (vea la figura 6), pero el usuario puede elegir una página maestra diferente de la lista desplegable.

Content Pages are Displayed Using the Site.master Master Page

Figura 06: Las páginas de contenido se muestran utilizando la página maestra Site.master (haga clic para ver la imagen de tamaño completo)

Content Pages are Now Displayed Using the Alternate.master Master Page

Figura 07: Ahora las páginas de contenido se muestran utilizando la página maestra Alternate.master (haga clic para ver la imagen de tamaño completo)

Resumen

Cuando se visita una página de contenido, sus controles de contenido se fusionan con los controles ContentPlaceHolder de su página maestra. La página maestra de la página de contenido se indica mediante la propiedad MasterPageFile de la clase Page, que se asigna al atributo MasterPageFile de la directiva @Page durante la fase de inicialización. Como se muestra en este tutorial, podemos asignar un valor a la propiedad MasterPageFile siempre que lo hagamos antes del final de la fase PreInit. La posibilidad de especificar mediante programación la página maestra abre la puerta a escenarios más avanzados, como la vinculación dinámica de una página de contenido a una página maestra en función de factores externos.

¡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

Muchos revisores han evaluado esta serie de tutoriales. El revisor principal de este tutorial fue Suchi Banerjee. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com