¿A quién le gusta escribir código de más? ¿A quién no le aburre el copy-paste después de un rato? Si hay herramientas que nos permiten evitar la repetición de código, bienvenidas sean.
Un archivo MasterPage en .NET es simple y sencillamente, una plantilla. Definimos el contenido general de nuestro sitio, dejamos un espacio para el contenido propio de cada página, y listo!
¿Cómo funciona un MasterPage?
Siguiendo las buenas prácticas del diseño web, lo correcto es tener una interfaz uniforme, enlazar al menos un archivo CSS para la hoja de estilos, un archivo JS para los scripts, un ícono para la página, etc. Lo más simple que se me pudiera ocurrir es crear una página con estos elementos y después copiarla cada vez que quiero crear una página nueva. Pero ¿qué pasa si se requiere un cambio en la estructura? ¿O si tengo que agregar un nuevo archivo de CSS o JS? ¿¡Tendré que cambiar cada página que he creado en mi sitio!? No si estamos trabajando en una plataforma como .NET.
Si en una página ASPX indicamos una referencia a un archivo MasterPage (con extensión .master), nuestro servidor buscará ese archivo y lo "combinará" con nuestra página. O dicho más correctamente, incorporará nuestra página al archivo Master para crear un solo documento y enviarlo como respuesta al cliente.
La ventaja de usar MasterPages es, además de evitarnos escribir lo mismo en cada página, que el mantenimiento de la interfaz general de nuestro sitio termina siendo algo muy sencillo, pues basta con editar un solo archivo que afectará a todas las páginas.
Cómo utilizar un MasterPage
El uso de los archivos MasterPage no tiene mucha ciencia. Agregamos un nuevo elemento al proyecto, y seleccionamos Master Page desde los templates. El archivo generado es muy similar a un archivo ASPX, con un par de diferencias:
- La declaración al inicio de la página, donde se muestra que es un MasterPage
- El elemento
<asp:ContentPlaceHolder>
El control ContentPlaceHolder
lo utilizamos para definir las partes de la estructura HTML dentro de las cuales irá el contenido de los archivos ASPX. Un archivo MasterPage es una plantilla, y como tal, necesita un espacio abierto que será usado por las páginas que la utilizan.
<%@ Master Language="C#" AutoEventWireup="true" CodeFile="MasterPage.master.cs" Inherits="MasterPage" %> <!DOCTYPE html> <html> <head runat="server"> <title></title> <link rel="stylesheet" href="<%= ResolveClientUrl("~/css/main.css") %>" /> <script src="<%= ResolveClientUrl("~/scripts/script.js") %>"></script> <asp:ContentPlaceHolder ID="head" runat="server" /> </head> <body> <header> <h1>Mi página</h1> </header> <nav> <asp:Menu ID="main_menu" runat="server" /> </nav> <article> <form id="form1" runat="server"> <asp:ContentPlaceHolder ID="container" runat="server" /> </form> </article> </body> </html>
Los controles ContentPlaceHolder
pueden ser colocados en cualquier parte de la página, en el lugar que será llenado por las páginas web. En este caso, estoy colocando uno dentro del <head>
, para poder agregar elementos al encabezado propios de una página (por ejemplo, una hoja de estilos exclusiva para una página en particular), y pongo el otro control dentro de <article>
, ya que el contenido de cada página estará dentro de ese elemento.
Otro punto importante es la forma en que enlazo archivos como la hoja de estilos y el script. Lo contenido entre -etiquetas <% %>
es código de servidor (Server-side code), el cual, como su nombre lo indica, incluye instrucciones en el lenguaje de servidor (VB.NET o C#) que son ejecutadas para generar un resultado que se reflejará en la página. Cuando se inicia con <%=
, significa que el resultado generado en esa instrucción se imprime en la página. Es decir, si yo escribo <p><%= variable.ToString() ></p>
, al cargar la página, el usuario verá el valor de variable
dentro de un párrafo.
En el caso de mi MasterPage, uso la instrucción ResolveClientUrl
para generar una ruta relativa al archivo que quiero enlazar.
¿Para qué hacemos esto?
Supongamos que tenemos nuestra MasterPage en la raíz del sitio, una carpeta styles con las hojas de estilo, y las páginas ASPX en diversas carpetas y directamente en la raíz. Si en mi MasterPage tengo la referencia a mi hoja de estilos así:
href="styles/style.css"
¿qué pasará si la página que usa el MasterPage está dentro de una carpeta, digamos, pages? El origen de esta ruta será la misma carpeta pages, por lo que buscaría la hoja de estilos en una ruta inexistente.
Con ResolveClientUrl
, le indicamos una ruta partiendo de la raíz, representada con el símbolo ~
, y el servidor se encargará de crear la ruta relativa desde nuestra página hacia el archivo o carpeta enlazado, ya sea styles/style.css
, ../styles/style.css
, o como sea necesario.
ResolveClientUrl
puede ser utilizado en cualquier situación donde necesitemos una ruta, ya sea un link, script, imagen, objeto embedido, etc. Incluso si necesitamos, por ejemplo, indicar la ruta a un objeto desde una función de javascript, podemos usar la misma instrucción en el contenido de la etiqueta <script>
.
La Master Page también puede contener código de servidor, que afectará a los elementos dentro de la misma. Podemos, por ejemplo, validar la sesión del usuario, y redirigirlo a una página de login, o incluir el código para generar el menú dinámicamente.
Habiendo explicado el elemento Master Page, podemos continuar con su aplicación en el sitio.
Al añadir un nuevo elemento de tipo WebForm, hay una opción que me permite seleccionar la MasterPage que quiero utilizar.
Al marcar esa casilla, lo siguiente será seleccionar el archivo .master que deseamos, para proseguir con la creación del archivo .aspx. La página generada quedaría de esta forma:
<%@ Page Title="" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" Runat="Server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="container" Runat="Server"> </asp:Content>
Las diferencias entre una página ASPX usando Master Page frente a una que no lo hace, son obvias. La primera es el atributo MasterPageFile en la etiqueta superior del archivo, que fácilmente podemos identificar como la ruta al archivo .master que indicamos al crear la página. De hecho, una página ya hecha también puede ser incorporada a una plantilla, si agregamos este atributo.
La otra diferencia (y la primera en notarse), es que la estructura de la página no se generó, y en su lugar tenemos dos etiquetas <asp:Content>
. En el caso de agregar el atributo MasterPageFile a una página existente, comenzará a mostrarnos error en la estructura, desde la etiqueta DOCTYPE
. En ambos casos, lo que sucede es que una página que utiliza una MasterPage no puede llevar la estructura entera de una página, sino únicamente el contenido que reemplazará a los ContentPlaceHolders
definidos en la plantilla, como comenté anteriormente. Por eso en mi caso estamos viendo dos controles Content
, el primero hace referencia al contenedor de nombre head, y el segundo, a container, según lo indicado en el atributo ContentPlaceHolderID
de cada uno.
El atributo ID no es necesario en las etiquetas Content
. Podemos omitir cualquiera de estos controles, si no es necesario, pero lo que no podemos hacer es indicar uno que no haga referencia a ningún ContentPlaceHolder en la Master Page.
Otra cosa que recomiendo es corregir los atributos Runat
, cambiando su nombre y valor a minúsculas. Hace tiempo tuve un problema en el que no se cargaban las páginas que usaban una Master Page, y al final descubrimos que el problema eran las mayúsculas en esos atributos. No me he topado con ese problema nuevamente, pero ya tengo la costumbre de hacer ese cambio siempre que trabajo en ASP.NET.
Dentro de cada control Content
puedo agregar contenido HTML válido para el elemento padre del ContentPlaceHolder
correspondiente.
<%@ Page Title="Mi página inicial" Language="C#" MasterPageFile="~/MasterPage.master" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server"> <style> h1{text-align:center} </style> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="container" runat="server"> <h2>Contenido de mi página</h2> <p>Primera página del sitio web.</p> </asp:Content>
El mismo Intellisense nos muestra los elementos que podemos utilizar dentro de cada Content
.
Al enviar nuestra página al usuario, él recibirá todo el código combinado de la MasterPage y de la página como uno sólo:
<!DOCTYPE html> <html> <head> <title>Mi página inicial</title> <link rel="stylesheet" href="css/main.css" /> <script src="scripts/script.js"></script> <style> h1{text-align:center} </style> </head> <body> <header> <h1>Mi página</h1> </header> <nav> <ul id="main_menu" /> </nav> <article> <form id="form1"> <h2>Contenido de mi página</h2> <p>Primera página del sitio web.</p> </form> </article> </body> </html>
El atributo Title
del archivo .aspx determinará el título de la página. En caso de omitirlo, se usará el definido en el archivo .master. En caso de no tener ninguno, el atributo quedará vacío.
Podemos tener tantos ContentPlaceHolders
en una plantilla como lo necesitemos. Incluso podemos tener Master Pages anidadas (una mater page que utilice otra). Este elemento es de mucha utilidad para ASP.NET, y no es muy compleja. Solo basta con definir bien la estructura de nuestras páginas y determinar lo que será global para el sitio. Lo demás es exactamente igual que trabajando con puros ASPX.
Colega, ¿cómo debo definir mi Content si en el Master defino un control que debe aparecen en todas mis páginas pero con la libertad de poner cualquier otro control en las páginas hijas de esta Master?
Los controles que agregues al MasterPage (fuera de los ContentPlaceHolder) son independientes de las páginas hijas.
Si agregas un <asp:TextBox/> en el MasterPage, podrás modificar su valor dentro del code-behind del mismo Master, y aparecerá con esas modificaciones cuando cargues una página. A su vez, los controles que crees en las páginas ASPX los podrás manipular desde el code-behind de éstas.
No estoy seguro de haber entendido la pregunta. Espero que con eso haya aclarado tu duda.
Disculpa, una pregunta, si deseo poner un content place en otra aspx que no sea la plantilla principal, ¿como puedo visualizarlo en la masterpage? Es decir, que el contenido de la principal y la secundaria se muestren en la misma página.