Шаблоны оформления

Прежде, чем приступить к использованию функциональных возможностей веб-приложения, пользователь складывает свое отношение к приложению, основываясь на внешнем виде страниц, решая для себя, удобно ли для него расположение элементов управление и не рябит ли у него в глазах от дизайнерских изысков. Поэтому грамотный дизайн интерфейса является едва ли менее важным, функциональные возможности.

Задача программиста состоит в написании программного кода, а художника-дизайнера в создании HTML верстки. При этом в больших проектах часто требуется, чтобы работа над кодом и шала параллельно над кодом и над HTML представлением одной и той же страницы.

В этой главе в качестве демонстрационного приложения и примера использования новых возможностей ASP.NET 2.0 будет создано веб-приложение для ведения дневников (блогов). В примерах этой статьи будет создан скелет дизайна подобного приложения.

Шаблоны оформления в ASP и ASP.NET 1.x


В классическом ASP для выполнения подобной задачи существовал стандартный метод с использованием директивы Include веб-сервера. Например, выделяются общие черты дизайна страниц веб-сайта, и HTML код шапки и нижней части страницы помещается в файлы header.html и footer.html, соответственно. При этом для всех динамических страниц создается шаблон, который включает в себя директивы Include и пути к соответствующим файлам с HTML разметкой.

<%@ LANGUAGE = VBScript %>
  <%
Option Explicit %>
  <!--
#include virtual="include/header.html" -->
 
<!--#include virtual="include/footer.html" -->

Программный код и HTML представление, специфичной для каждой конкретной страницы помещается между директивами include.

Этот подход позволял отделить большую часть HTML представления от кода программной логики и решить проблему распределения работы между программистом и дизайнером. Но у данного подхода существуют два значительных недостатка. Во-первых, HTML тэги, могут быть открыты во включаемом файле, а закрыты в основном файле, что практически полностью исключает поддержку визуального редактирования. Во-вторых, в данный сценарий невозможно подогнать под объектно-ориентированную модель и, таким образом, при попытке применить такую же модель к ASP.NET теряются преимущества объектно-ориентированного программирования.

В ASP.NET 1.x общие элементы оформления включают в пользовательские элементы управления, которые затем регистрируют для использования в коде представления каждой страницы с использованием директивы Register.

Приведенный выше пример может быть адаптирован для ASP.NET 1.x с использованием пользовательских элементов управления Header.ascx и Footer.ascx. Тогда код шаблона страницы будет выглядеть следующим образом.

<%@ Page %>
  <%@
Register TagPrefix="Design" TagName="Header" Src="~/Controls/Header.ascx" %>
  <%@
Register TagPrefix="Design" TagName="Footer" Src="~/Controls/Footer.ascx" %>
  <
Design:Header runat="server" ID="Header" />
  <
Design:Footer runat="server" ID="Footer" />

Этот подход достигает своей основной цели – код программной логики можно изменять не затрагивая шаблонов шапки и нижней части страницы, а при изменении кода или представления пользовательских элементов, они вступают в силу для всех страниц веб-приложения. Кроме того, удобство этого подхода в том, что страницу легко преобразовать в пользовательский элемент управления, всего лишь заменив директиву Page, на директиву Control, а также, в данном подходе не теряются возможности объектно-ориентированного программирования.

Но, стоит отметить и существующие недостатки. При внесении изменений в название методов и свойств элемента управления необходимо вручную внести изменения во все страницы, использующие этот элемент управления, а значит заново компилировать сборку приложения. Также, изменение кода страниц вызовет перекомпиляцию динамически создаваемых классов страниц на сервере.

Помимо этого, в этом подходе сохраняется проблема с разделением открывающих и закрывающих тегов между разными элементами управления, если необходимо, чтобы элементы управления создавали интерфейс всей страницы в целом.
Шаблоны оформления в ASP.NET 2.0

В ASP.NET 2.0 для поддержки шаблонов оформления создана новая технология, называемая Master Pages. Этот подход близок к подходу с использованием пользовательских элементов управления, но заключает в себе существенные преимущества. Для разработчика больше нет необходимости преобразовывать страницы в пользовательские элементы управления, что существенно упрощает и ускоряет разработку и отладку. Работа дизайнера также существенно упрощается, поскольку он может спокойно работать с кодом HTML представления, включив в него лишь несколько ASP.NET элементов, реализующих технологию Master Pages.
Архитектура шаблона оформления

Шаблон оформления в ASP.NET 2.0 представляет собой файл, содержащий HTML представления и элементы управления ASP.NET 2.0, определяющий внешний вид одной или нескольких (а возможно и всех) страниц веб-приложения. Шаблон оформления по своей структуре идентичен странице, но вместо директивы Page используется директива Master, а также в шаблоне оформления используются специальные элементы управления ContentPlaceholder. Страницы, использующие шаблон оформления, должны содержать элементы управления Content и вся разметка, специфичная для страницы должна быть помещена в эти элементы управления. Стоит отметить, что размещение кода разметки или элементов управления вне элементов Content в странице, использующей шаблоны оформления, приведет к ошибке времени компиляции.

При запросе к странице, использующей шаблон оформления, произойдет замещение элементов управления ContentPlaceholder шаблона на код представления, генерируемый страницей. Этот процесс представлен на Рис. 1.


Рис. 1 Объединение страницы и шаблона

Структура шаблона и страницы


Рассмотрим код представления простейшего шаблона оформления и простейшей страницы, использующей данный шаблон. Эти шаблон и страница реализуют пример, приведенный при рассмотрении использования шаблонов оформления в ASP и ASP.NET 1.x. Здесь шапка и нижняя часть страницы включен в файл шаблона.

Файл Master.master

<%@ Master %>
  <
html>
          <
head runat=”server”>
                <
title>Это Шаблон</title>
          </
head>
          <
body>
                <
form runat=”server” ID=”frmMain”>
                  <
asp:ContentPlaceHolder runat=”server” ID=”PageContent”>
                  </
asp:contentplaceholder>
                </
form>
                <
p>Copyrght © 2006 GM</p>
          </
body>
  </
html>
[/
CDOE

Файл Default.aspx


 
<%@ Page MasterPageFile="~/Master.master" Title="Это страница" %>
  <
asp:Content runat=”server” ContentPlaceHolderID =”PageContent”>
    <
h1>Hello world!</h1>
  </
asp:content>

При обращении к странице Default.aspx среда выполнения ASP.NET 2.0 выполнит объединение страницы и шаблона, и результирующий HTML будет выглядеть следующим образом.

<html>
          <
head runat=”server”>
                  <
title>Это Шаблон</title>
          </
head>
          <
body>
                  <
form runat=”server” ID=”frmMain”>
                          <
h1>Hello world!</h1
                  
</form>
                  <
p>Copyrght © 2006 GM</p>
          </
body>
  </
html>

Этот простейший пример демонстрирует общий принцип работы шаблона оформления, но уже из этого примера очевидны преимущества шаблонов оформления в ASP.NET 2.0: объектная ориентация, возможность разделить код шаблона и код страницы, возможность поддержки в визуальном редакторе. Помимо этого стоит упомянуть, что ASP.NET 2.0 поддерживает вложенные шаблоны оформления и предоставляет удобные механизмы для назначения шаблонов для большого количества страниц. Об этом будет написано далее в этой главе, при детальном рассмотрении взаимодействия шаблонов оформления и страниц.

Механизм объединения шаблона и страницы


При обращении к странице, использующей шаблон оформления, среда выполнения ASP.NET проверяет наличие файла шаблона и выстраивает иерархию шаблонов, если используются вложенные шаблоны оформления. Информация о зависимостях записывается в файл с расширением .compiled в директории для временных файлов ASP.NET. Примерная структура файла для приведенного выше примера приведена ниже.

<?xml version="1.0" encoding="utf-8"?>
  <preserve resultType="3" virtualPath="/TestWeb/Default.aspx" hash="fffffff5007efec4" filehash="6029956f826994b1" flags="110000" assembly="App_Web_whm_iyzn" type="ASP.default_aspx">
      <filedeps>
          <filedep name="/TestWeb/Default.aspx" />
          <filedep name="/TestWeb/Default.aspx.cs" />
          <filedep name="/TestWeb/Master.master" />
          <filedep name="/TestWeb/Master.master.cs" />
      </filedeps>
  </preserve>

Затем среда выполнения создает динамический класс для шаблона оформления. Этот класс также можно найти в директории для временных файлов ASP.NET.

public partial class Master : System.Web.UI.MasterPage
 
{
      protected
void Page_Load(object sender, EventArgs e)
      {
      }
  }

Класс шаблона наследует классу System.Web.UI.MasterPage, который, в свою очередь, является производным класса UserControl.


Рис. 2 Иерархия наследования класса MasterPage

Если файл шаблона содержит какие-либо элементы управления, то при генерации класса будут добавлены соответствующие свойства. Например, для определенных в примере элементов управления Form и ContentPlaceholder.

protected global::System.Web.UI.WebControls.ContentPlaceHolder PageContent;
  protected global::
System.Web.UI.HtmlControls.HtmlForm frmMain;

Помимо этого, класс будет расширен за счет добавления свойств, событий и методов, необходимых для реализации собственной функциональности ASP.NET. Для использования содержимого страниц, использующих шаблон, добавляются свойства типа ITemplate. Которым в дальнейшем будет присвоен сгенерированный классом страницы код.

private System.Web.UI.ITemplate @__Template_PageContent;

Так же, как и для любой страницы ASP.NET, для класса шаблона будет сгенерирован код создающий экземпляры элементов управления, используемых в шаблоне. Проследим этот процесс на примере элемента управления PageContent, для которого средой выполнения было создано одноименное свойство. Вначале создается экземпляр этого элемента управления.

global::System.Web.UI.WebControls.ContentPlaceHolder @__ctrl;
  @
__ctrl = new global::System.Web.UI.WebControls.ContentPlaceHolder();
 
this.PageContent = @__ctrl;
  @
__ctrl.ID = "PageContent";

Затем с помощью вызова метода InstantiateIn, определенного в интерфейсе ITemplate элементы управления, определенные на странице, использующей шаблон, добавляются в элемент ContentPlaceHolder.

if ((this.ContentTemplates != null))
  {
 
this.@__Template_PageContent = ((System.Web.UI.ITemplate)(this.ContentTemplates["PageContent"]));
  }
  if ((
this.@__Template_PageContent != null))
  {
 
this.@__Template_PageContent.InstantiateIn(@__ctrl);
  }
  else
  {
 
System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
  @
__parser.AddParsedSubObject(new 
 
System.Web.UI.LiteralControl("rn        "));
  }

Задача метода InstantiateIn состоит в том, чтобы добавить элементы управления в контейнер. Например, простейшая реализация метода может быть такой:

public void InstantiateIn(Control ctrl)
  {
      
Button btn = new Button();
      
btn.Click += new EventHandler(this.Button_Click);
      
ctrl.Controls.Add(btn);
  }

Таким образом, шаблон заполняется содержимым элементов Content, определенных в ASP.NET странице. Сопоставление содержимого элементов Content производится по заданному свойству ContentPlaceHolderID. И в результате структуру полученного объекта можно представить следующей диаграммой:


Рис. 3 Схема объединения шаблона и страницы

Из приведенного выше описания и диаграммы, очевидно, что не шаблон включает в себя страницу, а страница включает в себя шаблон, элементы управления ContentPlaceholder которого заполняются элементами управления, определенными в странице. Можно проследить аналогию с методикой организации шаблонов с помощью элементов управления, поскольку шаблон оформления в ASP.NET 2.0 является, по своей сути, элементом управления с расширенной функциональностью. Единственное от методики с использованием пользовательских элементов управления состоит в том, что элемент Form определяется в файле шаблона оформления.

Из всего описанного выше, следует, что при использовании шаблонов оформления жизненный цикл страницы не изменится. Порядок событий Init и Load страницы и шаблона будет таким же, как и для любого пользовательского элемента управления: событие Init для шаблона возникает раньше, чем для страницы, а Load наоборот, сначала для страницы, затем для шаблона.

Создание шаблона оформления


Имея на руках готовую верстку общего дизайна веб-приложения можно приступить к созданию шаблонов оформления. Прежде всего, нужно выделить все повторяющиеся элементы оформления страниц, такие как шапки, нижние части страниц, статические меню и подобные фрагменты внешнего вида страниц.

Для того, чтобы создать основной шаблон оформления приложения определимся с расположением активных частей на странице и выделим блоки, которые будут общими для всех страниц.


Рис. 4. Схема элементов дизайна

Логично предположить, что области «Шапка», «Авторские права» и «Навигация» во время работы приложения будут подвергаться незначительным изменениям и их можно выделить в шаблон оформления.

В области активного содержимого помещаем элемент управления ContentPlaceHolder и задаем его содержимое по умолчанию.

<asp:ContentPlaceHolder runat="server" ID="PageContent">
  <
p>No content available.</p>
  </
asp:ContentPlaceHolder>

Поскольку шаблон является элементом управления, то его свойства могут быть расширены программно, также как и любого другого элемента управления. В файл программной логики шаблона добавляется код соответствующих свойств, после чего к ним можно получить доступ в странице, использующей шаблон.

private int _UserID = 0;
  private
string _BlogTitle = "The OpenBlog";
  public
string BlogTitle
 
{
   
get { return _BlogTitle; }
   
set { _BlogTitle = value; }
  }
  public
int UserID
 
{
   
set { _UserID = value; }
  }

Для того, чтобы обращаться к этим свойства в странице, использующей шаблон используется свойство Master объекта Page. Но, при этом, нужно помнить о необходимости выполнения приведения типа объекта Master:

((Themes_MasterPages_DefaultMaster)Master).UserID = UserID;

Вложенные шаблоны


Следует отметить, что директива Master имеет те же атрибуты, что и директива Page: допустимо указать атрибут MasterPage и, таким образом, создать иерархическую структуру вложенных шаблонов оформления. Для этого в родительском шаблоне создается элемент управления ContentPlaceHolder, в дочернем шаблоне элемент Content, который, в свою очередь может также содержать элементы ContentPlaceHolder. Однако, стоит отметить, что редактор Visual Studio 2005 не поддерживает визуального редактирования вложенных шаблонов.


Рис. 6. Схема вложенных шаблонов

Использование шаблонов оформления


Как уже было сказано выше, для того, чтобы страница подходила для шаблона, она должна содержать элемент управления Content с установленным свойством ContentPlaceHolderID. Но ассоциировать конкретную страницу с шаблоном оформления можно несколькими способами.

Первый способ был использован в примере выше и состоит в непосредственном указании пути к файлу шаблона в атрибуте MasterPageFile директивы Page.

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

Второй способ заключается в назначении шаблона оформления сразу всем страницам в определенной директории или для всего веб-приложения в файле конфигурации приложения web.config.

<system.web>
        <
pages masterPageFile="~/Themes/Master.master" />
  </
system.web>

Необходимо обратить внимание, что если шаблон оформления устанавливается в файле web.config директории или всего приложения, то все ASP.NET страницы в директории или во всем приложении, соответственно, должны содержать элементы Content. Страница может содержать меньше элементов Content, чем элементов ContentPlaceHolder, содержащихся в шаблоне, но страницы, содержащие элементы управления или код разметки вне элементов Content будут выбрасывать исключение. И необходимо помнить, что в сконфигурированное таким образом приложение нельзя в будущем будет добавить обычную страницу, е использующую шаблоны.

Третий способ связывания страницы и шаблона заключается в динамическом присваивании значения свойству MasterPageFile непосредственно в коде. Для изменения глобальных параметров страницы, код должен быть выполнен до начала обработки страницы, поэтому установить свойство MasterPageFile можно только в обработчике Page_PreInit. Попытка изменить значение MasterPageFile в обработчиках Page_Init и Page_Load приведет к выбрасыванию исключения.

Page.MasterPageFile = "~/Themes/Master2.master";

Однако если установлен атрибут MasterPageFile директивы Page, то попытка выполнить приведенный выше код приведет к исключению, поскольку директива Page обрабатывается до начала выполнения кода страницы.

Заключение


В данной статье было рассказано о технологии шаблонов оформления ASP.NET 2.0, которая пришла на смену использованию пользовательских элементов управления. В ASP.NET 2.0 разделение кода и представления становится простым и приятным занятием, поскольку практически всю работу берет на себя среда выполнения.

Автор: Gaidar Magdanurov