Усовершенствованные мастер-страницы

107

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

Взаимодействие с классом мастер-страницы

Еще одна проблема с мастер-страницами связана с тем, каким образом их модель определяет необходимость копирования чего-либо на каждую страницу (в этом случае соответствующий элемент включается в мастер-страницу) или его изменения на каждой странице (при этом добавляется ContentPlaceHolder для этого содержимого и информация включается в каждую страницу содержимого). Этот способ различения подходит для многих страниц, но не будет работать, если требуется организовать более детальное взаимодействие между мастер-страницей и страницами содержимого.

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

Чтобы подобные сценарии могли работать, понадобится установить некоторую степень программного взаимодействия между страницей содержимого и мастер-страницей. Это не слишком сложно, поскольку можно обращаться к текущему экземпляру своей мастер-страницы с помощью свойства Page.Master, как было описано в предыдущей статье.

Первым шагом в организации взаимодействия между страницей содержимого и мастер-страницей является добавление в класс мастер-страницы соответствующих общедоступных свойств или методов. Страница содержимого сможет устанавливать эти свойства или вызывать эти методы. Например, нужно обеспечить возможность настройки текста баннера (пример этой мастер страницы мы рассмотрели ранее), но вы не хотите, чтобы страница содержимого вставляла в него содержимое любого типа. Вместо этого текст должен быть ограничен одной описательной строкой. Чтобы решить эту задачу, в верхний колонтитул можно добавить серверный элемент Label и предоставить к нему доступ через свойство BannerText в классе мастер-страницы:

<!-- Файл SiteTemplate.master -->
<asp:ContentPlaceHolder ID="TitleContent" runat="server">
	<h1 style="font-size:20px; text-align:center" id="myheader" runat="server">My Site</h1>
</asp:ContentPlaceHolder>
public string BannerText
{
        set { myheader.InnerText = value; }
        get { return myheader.InnerText; }
}

Теперь страница содержимого может изменять текст. Единственная загвоздка в том, что свойство Master возвращает объект, относящийся к обобщенному классу MasterPage. Чтобы получить доступ к любому из добавленных специальных членов, его потребуется привести к классу конкретной мастер-страницы:

protected void Page_Load(object sender, System.EventArgs e)
{
        SiteTemplate master = (SiteTemplate)this.Master;
        master.BannerText = "Страница #1";
}

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

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

Теперь при доступе к мастер-странице можно использовать более простой строго типизированный код:

protected void Page_Load(object sender, System.EventArgs e)
{
	this.Master.BannerText = "Страница #1";
}

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

Доступ к отдельному элементу управления на мастер-странице можно также получить "в лоб". С использованием метода MasterPage.FindControl() производится поиск нужного объекта на основе его уникального имени. Если есть элемент управления, его можно модифицировать напрямую. Ниже показан пример:

protected void Page_Load(object sender, System.EventArgs e)
{
        HtmlGenericControl header = 
            this.Master.FindControl("myheader") as HtmlGenericControl;

        if (header != null)
            header.InnerText = "Страница #1";
}

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

Динамическая настройка мастер-страницы

В некоторых случаях мастер-страница должна изменяться "на лету". Это может понадобиться в следующих случаях:

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

Этот прием можно реализовать почти так же, как динамические темы, о чем уже рассказывалось ранее. Однако здесь возникает потенциальная опасность - страница содержимого не обязательно полностью совместима с произвольной мастер-страницей. Если страница содержимого включает дескриптор Content, который не соответствует элементу ContentPlaceHolder в мастер-странице, возникнет ошибка. Для предотвращения этой проблемы все мастер-страницы, устанавливаемые динамически, должны включать одни и те же заполнители.

Вложение мастер-страниц

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

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Root.master.cs" Inherits="Root" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body style="background: #ccffff">
    <form id="form1" runat="server">
    <div>
        <h1>Корневая мастер-страница</h1>
        <asp:ContentPlaceHolder id="RootContent" runat="server" />
    </div>
    </form>
</body>
</html>

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

<%@ Master Language="C#" AutoEventWireup="true" CodeFile="Second.master.cs" 
    Inherits="Second" MasterPageFile="~/Root.master" %>

<asp:Content ContentPlaceHolderID="RootContent" runat="server">
    <div style="background: #fbccff">
        <h2>Мастер-страница второго уровня</h2>
        <asp:ContentPlaceHolder ID="SecondLevel" runat="server" />
    </div>
</asp:Content>

Атрибут MasterPageFile не нужно добавлять в мастер-страницу вручную. Вместо этого во время создания второй мастер-страницы можно отметить флажок Select Master Page.

Не исключено, что вам может понадобиться создать нескольких версий второй мастер-страницы - по одной для каждого раздела веб-сайта. Эти версии должны были бы получать один и тот же стандартный заголовок. И, наконец, каждая страница содержимого может использовать одну из мастер-страниц второго уровня для стандартизации своей компоновки:

<%@ Page Language="C#" MasterPageFile="~/Second.master" Title="Основы ASP.NET"
     AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default"  %>

<asp:Content ContentPlaceHolderID="SecondLevel" runat="server">
    <div style="background:green; color:white; padding:6px; text-align:right">
        <span style="font-size: 14px">
            Контент страницы
        </span>
    </div>
</asp:Content>

Результат показан на рисунке ниже:

Страница содержимого, использующая вложенную мастер-страницу

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

Пройди тесты
Лучший чат для C# программистов