В
статье рассматривается методика построения системы шаблонов страниц
сайта в ASP.Net, основанная на применении пользовательских компонентов (Web
User Controls). Приложение к статье - исходный код простого сайта,
иллюстрирующий особенности указанной методики.
Описание проблемы
При создании сайта веб-дизайнер и/или программист сталкивается с
проблемой разделения общей (для многих страниц) и частной (для
конкретных страниц) информации. Общая информация - это модульная сетка
сайта (разбиение страниц на отдельные блоки, например, верхнее меню,
левое меню, центральная часть), листы стилей CSS, метаданные (ключевые
слова, описание и пр.), скрипты (JavaScript, VBScript), информационные
блоки, например, на всех страницах сайта в нижней части может
отображаться контактная информация, и т. д. Частная информация
конкретной страницы - это заголовок страницы, информация, размещаемая в
ее центральной части, например, простой текст или форма для ввода
данных, и т.д. Часто об общей информации говорят, что она представляет
собой шаблон страниц сайта, а частная информация каждой страницы -
конкретное содержание используемого шаблона.
Итак, шаблоны применяются для избежания дублирования общей информации
на страницах сайта. Как следствие, шаблоны позволяют ускорить и
упростить процесс разработки сайта, но еще более значима их роль в
процессе сопровождения и развития сайта. Применение шаблонов существенно
упрощает как изменение дизайна сайта, так и наращивание его
функциональных возможностей.
Существуют различные реализации систем для построения сайтов с
применением технологии шаблонов. Подобные системы иногда называют web
framework, правда, кроме шаблонов web framework содержит
массу дополнительных возможностей, в частности, разделение html-кода и
программного кода, MVC-архитектура, дополнительные функциональные
сервисы и API и т.д. В наиболее распространенных технологиях,
используемых в настоящее время для создания веб-приложений, можно
отметить несколько вариантов web framework. Например, в Java
используются такие системы, как
Struts Tiles,
Jakarta Turbine,
JPublish и др. В
PHP это Roadsend Site
Mananager, Smarty,
FastTemplate и др. В ASP это
ASP Template и др.
Примечательно, что в базовых технологиях JSP, PHP, ASP отсутствует
какая-либо встроенная стандартная реализация системы шаблонов -
производители почему-то ограничиваются предложением использовать
различные варианты Server Side Includes.
Например, в JSP используются:
<jsp:include page="{relativeURL | <%= expression %>}" flush="true| false" />
или
<%@ include file="relativeURL" %>.
В PHP это выглядит так:
<?php include 'filename'; ?>.
В ASP используются:
<!--#include virtual = "filename"-->
или
<!--#include file = "filename"-->.
Следует отметить, что Server Side Includes не являются
системой шаблонов в полном смысле, в частности, SSI не позволяют
избежать дублирования информации о модульной сетке сайта. Тем более
удивительно, что при очевидной важности шаблонов производители не
предлагают стандартной реализации для каждой технологии - все отмеченные
выше системы шаблонов для JSP, PHP и ASP разработаны либо в рамках
движения open source (www.sourceforge.net),
либо сторонними фирмами.
Интересно, что ситуация с шаблонами в ASP.Net не слишком отличается
от JSP, PHP, ASP. Проблема та же самая - в текущей версии ASP.Net нет
стандартного механизма построения шаблонов страниц, который был бы
рекомендован Microsoft и поддерживался бы на уровне среды разработки
Visual Studio.Net.
Возможно, в версии ASP.Net 2.0 подобный механизм будет предложен, но
пока приходится использовать различные частные решения.
В качестве таких решений в настоящее время разными разработчиками
предлагается использовать Server Side Includes (директивы
включения ASP остаются работоспособными и в ASP.Net), наследование
страниц ASP.Net с переписыванием метода Page.Render, включение
пользовательских компонентов (Web User Controls) в страницу.
Метод наследования страниц ASP.Net предполагает внедрение html-кода в
программный код, соответственно исключает всякую возможность визуального
редактирования шаблона страницы, чреват ошибками и не слишком удобен при
сопровождении сайта.
В случае использования пользовательских компонентов получаем
фактически более интеллектуальный аналог Server Side Includes -
компоненты могут содержать не просто статическую информацию, но и
некоторую программную логику. Заметим, что простое включение компонентов
не решает проблему вынесения модульной сетки сайта в шаблон страницы,
хотя и является существенным шагом вперед к созданию полноценной системы
шаблонов.
Решение проблемы
Рассмотрим минимальные требования, которым должна удовлетворять
система шаблонов страниц ASP.Net:
- На сайте может использоваться произвольное количество шаблонов
страниц.
- Должен обеспечиваться режим визуального редактирования любых
составляющих шаблона страницы в текущей версии Visual Studio .Net.
- Система шаблонов должна быть достаточно простой для освоения ее
веб-дизайнером.
- В простом варианте система шаблонов не должна требовать
использования базы данных для хранения информации о страницах и
шаблонах.
- Система шаблонов должна естественным образом встраиваться в
модель ASP.Net и не должна ограничивать возможности разработчика по
применению существующих технологий, т.е. должны сохраняться
возможности использования директив включения Server Side Includes,
веб-компонентов и т.д.
Предлагаемое решение для построения шаблонов удовлетворяет указанным
требованиям и базируется на использовании пользовательских компонентов (Web
User Controls) как для построения шаблонов страниц, так и для
формирования содержимого конкретных страниц.
Методику построения шаблонов изложим на примере простого сайта,
включающего:
- один общий шаблон для всех страниц, включающий верхнюю часть,
левую часть, центральную часть и нижнюю часть;
- главную страницу, на которой отображаются последние новости;
- страницу для добавления новостей;
- статическую страницу, содержащую простой текст.
Кроме того, в примере рассматривается использование пользовательского
компонента для создания шаблона левого меню навигации с целью
иллюстрации дополнительных возможностей рассматриваемой методики.
Исходные тексты примера приводятся в качестве приложения к статье.
Основной шаблон страниц сайта хранится в каталоге Templates и
представляет собой пользовательский компонент DefaultTemplate.ascx
с программным модулем DefaultTemplate.ascx.cs. Рассмотрим
содержание соответствующих файлов.
Templates/DefaultTemplate.ascx
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="DefaultTemplate.ascx.cs"
Inherits="GotDotNetTemplates.Templates.DefaultTemplate"
TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title><% = Title %></title>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<meta http-equiv="description" content="Здесь сейчас статическое описание,
не трудно представить, как сделать динамическое - См. Title">
<meta http-equiv="keywords" content=".Net, ASP.Net, C#, шаблоны страниц,
Templates, User Control ">
<link href="<%=Request.ApplicationPath%>/Styles/Default.css"
type="text/css" rel="stylesheet">
</head>
<body>
<form id="Default" method="post" runat="server">
<table cellSpacing="1" cellPadding="1" width="100%" border="1">
<tr>
<td noWrap align="middle" colspan="2">
<!--<Верхняя часть>-->
<p>Здесь в шаблоне размещается верхняя часть страницы (рекламный блок, меню
навигации и пр.)</p>
<!--</Верхняя часть>-->
</td>
</tr>
<tr>
<td noWrap width="20%" valign="top">
<!--<Левая часть>-->
<asp:PlaceHolder id="LeftPlaceHolder" runat="server"></asp:PlaceHolder>
<!--</Левая часть>-->
</td>
<td noWrap width="80%" valign="top">
<!--<Центральная часть>-->
<asp:placeholder id="CenterPlaceHolder" runat="server"></asp:placeholder>
<!--</Центральная часть>-->
</td>
</tr>
<tr>
<td noWrap align="middle" colspan="2">
<!--<Нижняя часть>-->
<p>Здесь в шаблоне размещается нижняя часть страницы (контактная информация,
информация о разработчиках и пр.)</p>
<!--</Нижняя часть>-->
</td>
</tr>
</table>
</form>
</body>
</html>
Пользовательский компонент DefaultTemplate.ascx включает в
себя html-теги верхнего уровня, в том числе <html>, <head>, <body>,
<form>, то есть те теги, которые обычно характерны не для компонентов, а
для страниц ASP.Net. В этом заключается основная особенность данного
способа построения шаблонов. Обратите внимание, что в теге <form>
отсутствует атрибут action, его вставляет среда выполнения при
окончательном формировании страницы. Подробнее об этом будет написано
ниже при рассмотрении кода главной страницы.
В шаблоне определены следующие области, данные для которых
предоставляются страницами, использующими шаблон:
- заголовок страницы - для вывода используется свойство Title
компонента
<title><% = Title %></title>
- левая часть - в эту область шаблона может загружаться любой
пользовательский компонент, в качестве контейнера используется
PlaceHolder
<!--<Левая часть>-->
<asp:PlaceHolder id="LeftPlaceHolder" runat="server"></asp:PlaceHolder>
<!--</Левая часть>-->
- центральная часть - аналогична левой части в том смысле, что и в
нее может загружаться любой пользовательский компонент, в качестве
контейнера также используется PlaceHolder
<!--<Центральная часть>-->
<asp:placeholder id="CenterPlaceHolder" runat="server"></asp:placeholder>
<!--</Центральная часть>-->
Все прочие области шаблона страницы заданы в нем статически - верхняя
часть, нижняя часть, метаданные, ссылка на общий для всех страниц сайта
файл с описанием стилевых таблиц Default.css. Последняя ссылка
имеет следующий вид:
<link href="<%=Request.ApplicationPath%>/Styles/Default.css" type="text/css" rel="stylesheet">
Шаблон может использоваться в странице сайта, которая находится в
каталоге с произвольным уровнем вложенности относительно корневого
каталога веб-приложения. Соответственно, в шаблоне необходимо
использовать абсолютные ссылки, для этой цели и применяется
<%=Request.ApplicationPath%> - возвращает виртуальный путь к
корневому каталогу приложения. Например:
<a href="<%=Request.ApplicationPath%>/AddNewsItem.aspx">Добавить новость</a>
Рассмотрим теперь исходный текст программного модуля
DefaultTemplate.ascx.cs.
Templates/DefaultTemplate.ascx.cs
public abstract class DefaultTemplate: System.Web.UI.UserControl {
protected string title;
protected string leftModuleUrl;
protected System.Web.UI.WebControls.PlaceHolder LeftPlaceHolder;
protected System.Web.UI.WebControls.PlaceHolder CenterPlaceHolder;
protected string centerModuleUrl;
private void Page_Load(object sender, System.EventArgs e) {
LeftPlaceHolder.Controls.Add(LoadControl(LeftModuleUrl));
CenterPlaceHolder.Controls.Add(LoadControl(CenterModuleUrl));
}
public string Title {
get { return title; }
set { title = value; }
}
public string LeftModuleUrl {
get { return leftModuleUrl; }
set { leftModuleUrl = value; }
}
public string CenterModuleUrl {
get { return centerModuleUrl; }
set { centerModuleUrl = value; }
}
}
В классе шаблона определены следующие свойства:
- Title - заголовок страницы.
- LeftModuleUrl - url пользовательского компонента,
загружаемого в левую часть шаблона.
- CenterModuleUrl - url пользовательского компонента,
загружаемого в центральную часть шаблона.
Применение свойств для определения замещаемых областей в шаблоне
является еще одной ключевой особенностью данного способа построения
шаблонов. Страницы, использующие шаблон, устанавливают конкретные
значения его свойств, формируя таким образом свое содержимое. В шаблоне
DefaultTemplate.ascx присутствуют лишь простые строковые
свойства, однако, никаких ограничений нет, могут использоваться и
сложные типы данных. Пример шаблона, в котором присутствует свойство
сложного типа, будет приведен ниже - шаблон левого меню.
Следует отметить, что логика использования свойств шаблона для
формирования содержимого страницы программируется в классе шаблона и
может быть совершенно произвольной. Например, для загрузки в левую и
центральную части шаблона DefaultTemplate.ascx пользовательских
компонентов, соответствующих странице, необходимо задать значения
строковых свойств LeftModuleUrl, CenterModuleUrl,
непосредственная загрузка компонентов осуществляется в методе
Page_Load.
Рассмотрим далее применение описанного шаблона в конкретных страницах
сайта.
Default.aspx
<%@ Page language="c#" AutoEventWireup="false" %>
<%@ Register TagPrefix="template"
TagName="DefaultTemplate"
Src="Templates/DefaultTemplate.ascx" %>
<template:DefaultTemplate id="DefaultTemplate"
runat="server"
Title="GotDotNetTemplates - Главная страница"
LeftModuleUrl="~/LeftMenuModule.ascx"
CenterModuleUrl="~/DefaultCenterModule.ascx">
</template:DefaultTemplate>
На главной странице используется пользовательский компонент шаблона
DefaultTemplate.ascx, для которого устанавливаются конкретные
значения свойств. Символ "~", используемый при установке значений
свойств LeftModuleUrl и CenterModuleUrl, в ASP.Net
соответствует корневому каталогу веб-приложения. В левую часть шаблона
на главной странице загружается общее для всех страниц сайта меню
навигации LeftMenuModule.ascx, а в центральную часть -
DefaultCenterModule.ascx, который выводит последние новости из базы
данных.
Заметим, что для данного сайта меню навигации могло бы быть просто
прописано в шаблоне DefaultTemplate.ascx в виде статического
html-кода, и свойство LeftModuleUrl было бы ненужным, однако, в
целях иллюстрации потенциальных возможностей я искусственно пошел на
дополнительное усложнение шаблона страниц. Практически полезным подобное
усложнение будет для сайтов, включающих несколько разделов верхнего
уровня, каждый из которых имеет собственное левое меню навигации.
Вернемся теперь к вопросу вставки атрибута action для тега <form>,
определенного в шаблоне. Интересная особенность, которая, собственно, и
позволяет применять данную методику построения шаблонов, заключается в
том, что среда выполнения подставляет в качестве значения атрибута
action адрес той aspx-страницы, в которую вставлен пользовательский
компонент шаблона. Например, при формировании главной страницы среда
выполнения установит значение атрибута action = "/Default.aspx".
AddNewsItem.aspx
<%@ Page language="c#" AutoEventWireup="false"%>
<%@ Register TagPrefix="template"
TagName="DefaultTemplate"
Src="Templates/DefaultTemplate.ascx" %>
<template:DefaultTemplate id="DefaultTemplate"
runat="server"
Title="GotDotNetTemplates - Добавление новости"
LeftModuleUrl="~/LeftMenuModule.ascx"
CenterModuleUrl="~/AddNewsItemCenterModule.ascx">
</template:DefaultTemplate>
Код страницы для добавления новостей аналогичен коду главной
страницы, разница заключается только в устанавливаемых значениях свойств
шаблона.
Templates/LeftMenuTemplate.ascx
<%@ Control Language="c#" AutoEventWireup="false" Codebehind="LeftMenuTemplate.ascx.cs"
Inherits="GotDotNetTemplates.Templates.LeftMenuTemplate"
TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<asp:Repeater id="MenuRepeater" runat="server">
<ItemTemplate>
<tr>
<td width="100%" align="left" valign="center" nowrap>
<img src="<%=Request.ApplicationPath%>/Images/SubmenuPointer.gif"
alt="" width="10" height="9" hspace="4" vspace="0" border="0"
align="baseline">
<font class="menu"><a href="<%# DataBinder.Eval(Container, "DataItem.Url") %>">
<%# DataBinder.Eval(Container, "DataItem.Title") %></a></font>
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
</table>
В шаблоне левого меню используется Repeater для вывода пунктов
меню. В качестве источника данных для Repeater служит обычный
ArrayList, который заполняется вспомогательными объектами класса
LinkHelper (предназначен для хранения адреса и текста ссылки -
Url, Title) - см. ниже.
Templates/LeftMenuTemplate.ascx.cs
public abstract class LeftMenuTemplate: System.Web.UI.UserControl {
protected System.Web.UI.WebControls.Repeater MenuRepeater;
protected ArrayList links = new ArrayList();
private void Page_Load(object sender, System.EventArgs e) {
MenuRepeater.DataSource = links;
MenuRepeater.DataBind();
}
public ArrayList Links {
get { return links; }
}
}
В методе Page_Load устанавливается источник данных для
элемента Repeater.
Рассмотрим далее, каким образом используется данный шаблон в
пользовательском компоненте LeftMenuModule.ascx - общее для всех
страниц сайта меню навигации.
LeftMenuModule.ascx
<%@ Control Language="c#" AutoEventWireup="false"
Codebehind="LeftMenuModule.ascx.cs"
Inherits="GotDotNetTemplates.LeftMenuModule"
TargetSchema="http://schemas.microsoft.com/intellisense/ie5"%>
<%@ Register TagPrefix="template"
TagName="LeftMenuTemplate"
Src="Templates/LeftMenuTemplate.ascx" %>
<template:LeftMenuTemplate id="LeftMenuTemplate" runat="server"></template:LeftMenuTemplate>
Видно, что использование шаблона меню в пользовательском компоненте
аналогично использованию шаблона в aspx-странице. Следует отметить, что
свойства шаблона меню задаются не декларативно как в случае с шаблоном
страницы сайта, а программно - см. ниже.
LeftMenuModule.ascx.cs
public abstract class LeftMenuModule: System.Web.UI.UserControl {
protected LeftMenuTemplate LeftMenuTemplate;
private void Page_Load(object sender, System.EventArgs e) {
LeftMenuTemplate.Links.Add(new LinkHelper(Request.ApplicationPath +
"/Default.aspx", "Главная страница"));
LeftMenuTemplate.Links.Add(new LinkHelper(Request.ApplicationPath +
"/AddNewsItem.aspx", "Добавление новости"));
LeftMenuTemplate.Links.Add(new LinkHelper(Request.ApplicationPath +
"/StaticPage.aspx", "Статическая страница"));
}
}
В методе Page_Load заполняется список ссылок в шаблоне левого
меню, в список добавляются объекты LinkHelper для каждого пункта
меню. Программный способ установки свойств шаблона в методе Page_Load
следует применять в случае свойств сложных типов данных, которые нельзя
установить декларативно как в случае, например, простых строковых
свойств.
Templates/LinkHelper.cs
public class LinkHelper {
private string url;
private string title;
public LinkHelper(string url, string title) {
this.url = url;
this.title= title;
}
public string Url {
get { return url; }
set { url = value; }
}
public string Title {
get { return title; }
set { title = value; }
}
}
Данный класс предназначен для хранения атрибутов ссылок левого меню -
адреса и текста.
Рассмотрим, наконец, последнюю страницу сайта - страницу для вывода
некоторой статической информации StaticPage.aspx.
StaticPage.aspx
<%@ Page language="c#" AutoEventWireup="false" %>
<%@ OutputCache Duration="3600" VaryByParam="None" %>
<%@ Register TagPrefix="template"
TagName="DefaultTemplate"
Src="Templates/DefaultTemplate.ascx" %>
<template:DefaultTemplate id="DefaultTemplate"
runat="server"
Title="GotDotNetTemplates - Статическая страница"
LeftModuleUrl="~/LeftMenuModule.ascx"
CenterModuleUrl="~/StaticPageCenterModule.ascx">
</template:DefaultTemplate>
Особенность данной страницы - использование директивы OutputCache,
которая позволяет кэшировать страницу в течение длительного времени.
Рассмотренная методика построения шаблонов позволяет применять
кэширование, как на уровне страниц, так и на уровне отдельных блоков
страницы - пользовательских компонентов. Разумное использование
возможностей кэширования позволяет практически нивелировать накладные
расходы, связанные с динамической сборкой страниц.
Заключение
Рассмотренная методика построения шаблонов страниц достаточно проста,
универсальна и легко расширяема. В частности, ее несложно обобщить до
уровня системы управления контентом, для этого достаточно хранить в базе
данных информацию о шаблонах, их свойствах и значениях этих свойств для
конкретных документов сайта. Потребуется одна страница-контроллер,
которая будет формировать содержимое страницы по идентификатору
документа, передаваемому в строке url. Можно придумать и другие варианты
расширения, технология не накладывает практически никаких ограничений.