Структура страницы

121

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

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

При первом создании страницы (в ответ на HTTP-запрос) ASP.NET инспектирует файл .aspx. Для каждого обнаруживаемого там элемента с атрибутом runat="server" ASP.NET создает и настраивает объект элемента управления, после чего добавляет этот элемент управления в виде дочернего элемента управления страницы. Просмотреть все дочерние элементы управления страницы можно в коллекции Page.Controls.

Отображение дерева элементов управления

Ниже приведен пример кода, выполняющего поиск элементов управления. Всякий раз, когда этот код обнаруживает какой-то элемент управления, он использует команду Response.Write() для добавления в конец визуализируемой HTML-страницы информации о типе класса данного элемента управления и его идентификаторе, как показано ниже:

foreach (Control control in Page.Controls)
{
            Response.Write(control.GetType().ToString() + " - <b>" + control.ID + "</b><br/>");
}

Response.Write("<hr/>");

Чтобы протестировать этот код, можно добавить его к обработчику событий Page.Load. В этом случае преобразованное содержимое будет выведено в верхней части страницы перед элементами управления. Однако при ее запуске вы обратите внимание на неожиданное поведение.

Например, рассмотрим веб-форму, показанную на рисунке ниже, которая содержит несколько элементов управления, и некоторые из них организованы в панели с использованием веб-элемента управления под названием Panel. Она также содержит две строки статического текста HTML:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ControlTree.aspx.cs" Inherits="ControlTree" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Controls</title>
</head>
<body>
    <p><i>Элемент HTML (не веб-элемент управления ASP.NET).</i></p>
    <form id="ControlForm" method="post" runat="server">
        <div>
            <asp:Panel ID="MainPanel" runat="server" Height="112px">
                <p>
                    <asp:Button ID="Button1" runat="server" Text="Button1" />
                    <asp:Button ID="Button2" runat="server" Text="Button2" />
                    <asp:Button ID="Button3" runat="server" Text="Button3" />
                </p>
                <p>
                    <asp:Label ID="Label1" runat="server" Width="48px">Name:</asp:Label>
                    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
                </p>
            </asp:Panel>
            <p>
                <asp:Button ID="Button4" runat="server" Text="Button4" /></p>
        </div>
    </form>
    <p><i>Элемент HTML (не веб-элемент управления ASP.NET).</i></p>
</body>
</html>
Пример веб-страницы с несколькими элементами управления

Запустив эту страницу, вы увидите не полный перечень элементов управления, а всего лишь следующий список:

Элементы управления, находящиеся на самом верхнем уровне в странице

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

Объекты LiteralControl предлагают немногое в отношении функциональности. Например, вы не можете устанавливать информацию, связанную со стилями, такую как цвет и шрифт. У них также нет уникальных серверных идентификаторов. Однако можно манипулировать содержимым LiteralControl с использованием его свойства Text.

ASP.NET визуализирует страницу иерархическим образом. Сразу визуализируются только элементы управления наивысшего уровня. Если в этих элементах управления содержатся другие элементы управления, они имеют собственные свойства Controls, которые предоставляют доступ к их дочерним элементам управления. В рассматриваемой странице, как и во всех веб-формах ASP.NET, все элементы управления вложены в дескриптор <form>. Это означает, что для получения информации об имеющихся на этой странице серверных элементах управления необходимо исследовать коллекцию Controls класса HtmlForm.

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

В следующем коде показано, как должно выглядеть все решение:

    protected void Page_Load(object sender, System.EventArgs e)
    {
        DisplayControl(Page.Controls, 0);
        Response.Write("<hr/>");
    }

    private void DisplayControl(ControlCollection controls, int depth)
    {
        foreach (Control control in controls)
        {
            // Количество отступов в представлении дерева элементов управления
            Response.Write(new String('-', depth * 4) + "> ");

            // Отобразить элемент управления
            Response.Write(control.GetType().ToString() + " - <b>" +
              control.ID + "</b><br />");

            if (control.Controls != null)
            {
                DisplayControl(control.Controls, depth + 1);
            }
        }
    }

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

Дерево элементов управления на странице

Заголовок страницы

Как вы убедились, вы можете преобразовать любой HTML-элемент в серверный элемент управления с помощью атрибута runat="server", и страница может содержать неограниченное количество элементов управления HTML. В дополнение к добавляемым вами элементам управления веб-форма может также содержать отдельный элемент управления HtmlHead, предоставляющий серверный доступ к дескриптору <head>.

Дерево элементов управления, показанное в предыдущем примере, не содержит элемент управления HtmlHead, поскольку атрибут runat="server" не был применен к дескриптору <head> на странице. Однако по умолчанию Visual Studio всегда делает дескриптор <head> серверным элементом управления в отличие от предыдущих версий ASP.NET.

Как и в случае с другими серверными элементами управления, вы можете использовать элемент управления HtmlHead для программного изменения содержимого, преобразуемого в дескрипторе <head>. Различие заключается в том, что дескриптор <head> не соответствует фактическому содержимому, которое можно увидеть на веб-странице. Вместо этого он содержит другие детали вроде заголовка, дескрипторов метаданных (полезных для предоставления ключевых слов в поисковых системах) и ссылок на таблицы стилей. Для изменения любой из этих деталей применяется один из немногих членов класса HtmlHead, которые перечислены в таблице.

Полезные свойства класса HtmlHead
Свойство Описание
Title Заголовок HTML-страницы, который обычно отображается в строке заголовка браузера. Значение этого свойства можно изменять во время выполнения
Stylesheet Объект IStylesheet, который отвечает за определяемые в заголовке внутристрочные стили. Объект IStylesheet можно также использовать для динамического создания новых правил стиля путем написания кода, вызывающего его методы CreateStyleRule() и RegisterStyle()
Desсription Текст в метадескрипторе description, который применяется для создания описания веб-сайта в поисковых системах, подобных Google
Keywords Текст в метадескрипторе keywords. Когда-то этот метадескриптор использовался поисковыми системами для определения поисковых рейтингов для конкретных запросов, теперь он практически всеми игнорируется
Controls Используя эту коллекцию и класс HtmlMeta, можно программно добавлять и удалять дескрипторы метаданных. Это удобно, когда нужно добавить метадескрипторы, отличные от description и keywords

Ниже показан пример, в котором информация о названии, и дескрипторы метаданных устанавливаются динамически:

Page.Header.Title = "Структура страницы ASP.NET";
Page.Header.Description = "Описание возможностей ASP.NET по динамическому использованию элементов на странице";
Page.Header.Keywords = "C#, .NET, ASP.NET";

//А вот как можно добавить в заголовок другой метадескриптор
HtmlMeta metaTag = new HtmlMeta();
metaTag.HttpEquiv = "Content-Type";
metaTag.Content = "text/html; charset=utf-8";
Page.Header.Controls.Add(metaTag);

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

Создание динамического элемента управления

С использованием коллекции Controls можно программно создать и добавить к странице элемент управления. Ниже показан пример генерации новой кнопки и ее добавления в панель на странице:

// Создать новый объект кнопки. 
Button newButton = new Button();
        
// Присвоить некоторый текст и идентификатор для будущего извлечения. 
newButton.Text = "* Dynamic Button *"; 
newButton.ID = "newButton"; 

// Добавить кнопку к панели. 
MainPanel.Controls.Add(newButton);

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

Для получения большего контроля над расположением динамического элемента управления можно использовать PlaceHolder — элемент управления, целью которого является размещение других элементов управления. Если вы не добавите другие элементы управления в коллекцию Controls элемента управления PlaceHolder, он ничего не изменит на окончательной веб-странице. Visual Studio определяет визуальное представление по умолчанию в виде обычной метки во время проектирования, так что вы можете расположить элемент PlaceHolder там, где пожелаете. Подобным образом можно размещать динамический элемент управления среди других элементов управления:

// Добавить кнопку в PlaceHolder. 
PlaceHolder1.Controls.Add(newButton); 

При использовании динамических элементов управления следует помнить, что они будут существовать только до следующей обратной отправки. ASP.NET не будет повторно создавать динамически добавленный элемент управления. Если нужно создавать элемент управления несколько раз, это следует делать в обработчике событий Page.Load.

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

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

// Поиск кнопки в коллекции Page.Controls
Button foundButton = (Button)Page.FindControl("newButton");

// Удаление кнопки
if (foundButton != null)
    foundButton.Parent.Controls.Remove(foundButton);

Динамически добавленные элементы управления могут обрабатывать события. Понадобится лишь подключить обработчик событий с использованием кода делегата. Вы должны выполнять это в обработчике событий Page.Load. Как было показано ранее, все события, связанные с элементами управления, генерируются после события Page.Load. Если вы подождете дольше, обработчик событий будет подключен уже после запуска события, и вы не сможете на него отреагировать:

// Подключить обработчик событий Button.Click
newButton.Click += dynamicButton_Click;

Динамические элементы управления являются особо мощными, когда комбинируются вместе с пользовательскими элементами управления (т.е. блоками пользовательского интерфейса, которые допускают многократное использование и могут содержать в себе ряд элементов управления и HTML).

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