Программирование на ASP.NET

Web-формы

Разбить на страницы
Показывать лекцию целиком
Файлы к лекции Вы можете скачать здесь

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

В основе Web-форм HTML-страниц лежит дескриптор <form>...</form>. В активных страницах других систем, например PHP, страница допускает наличие на ней нескольких дескрипторов <form>...</form> (дескрипторов HTML-форм). В таком случае эти дескрипторы должны иметь атрибуты ID с разными значениями, чтобы поддерживающая активные страницы система могла различать, какая форма прислала на сервер данные, которые нужно обработать.

Страницы ASP.NET требуют, чтобы на каждой из них было не более одной формы (в принципе, форм на странице может быть несколько, но в каждый момент видимой Visible=True может быть только одна форма).

Но этого достаточно и для обычной HTML-страницы, дабы сколько бы элементов управления HTML-форма не содержала, с них всех собирается информация при подготовке обратной отсылки на сервер. Когда пользователь щелкает на кнопке Submit, броузер извлекает текущие значения последовательно с каждого элемента управления и формирует из них длинную строку пар ID=значение. Затем эта строка отправляется на сервер странице, указанной в атрибуте action дескриптора <form>, используя метод post, или метод get. Если атрибут action не указан, то данные отправляются той же странице. Сам HTML не устанавливает на количество форм никаких ограничений, а ASP.NET устанавливает и для одной активной страницы способна генерировать только одну HTML-форму <form></form>.

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

<html>
<head>
    <title>Посылка сообщений</title>
    <meta content="text/html; charset=windows-1251" http-equiv="Content-Type">
</head>
<body>
    <form method="get" name="form1">
        <p>
            Ваше сообщение:
            <input id="message" name="message" type="text" value="Hello, world!">
            <br>
            Кто посылает:
            <input id="autor" name="autor" type="text" value="CHETKOB B.M.">
        </p>
        <p>
            Кто примкнул к сообщению:
        </p>
        <ul>
            <li>
                <input id="P" name="P" type="checkbox" value="checkbox">
                Петров Петр </li>
            <li>
                <input id="I" name="I" type="checkbox" value="checkbox">
                Иванов Иван </li>
        </ul>
        <p>
            <input id="OK" type="submit" value="Submit">
        </p>
    </form>
</body>
</html>

Нажмите на кнопку Submit и посмотрите в поле адреса броузера. Форма отослала на сервер строку

WebForms.htm?message=Hello%2C+World%21autor=CHETKOB+B.M.OK=Submit

Поскольку в форме указан метод ?) оказались присоединены пары имя=значение, разделенные амперсандом ( ). Обратите внимание, что символы пробела, восклицательный знак, запятая (и если вы введете русский текст) заменяются двухбайтными кодами с предваряющим символом процента ( % ).

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

Мы уже говорили, что если такую страницу разместить на сервере, то не смотря на расширение .aspx, ASP.NET -машина ее все равно обрабатывать не будет. Для этого HTML-дескрипторы нужно сделать активными (серверными) добавлением атрибута runat="server". В связи с этим сделаем два определения: та страница, которая не содержит серверных дескрипторов, называется статической (пассивной). Если же на странице есть хоть один дескриптор с указанным атрибутом, то такая страница подлежит дальнейшей обработке системой ASP.NET (или иной) и называется динамической (активной).

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

Пример кодирования страницы

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

При помещении элементов с вкладки HTML на страницу атрибут runat="server" автоматически не добавляется и такой элемент средой на сервере обрабатываться не будет. Следовательно, кодировать на языке программирования его будет нельзя. Прежде элемент нужно пометить как серверный, введя напрямую указанный атрибут в дескрипторное представление элемента, или в оболочке в режиме Design на выделенном элементе вызвать контекстное меню и выполнить опцию Run As Server Control (сделать серверным элементом управления)

Если мы разрабатываем активную страницу "с нуля", то лучше сразу применять серверные элементы управления, где уже автоматически встроен дескриптор runat="server". И более того, если его убрать, это будет считаться ошибкой. Дескрипторы, представляющие серверные элементы, начинаются с метки asp:Тип_элемента, далее следуют атрибуты элемента.

Для примера создадим простую страницу.

  • Создайте новый сайт командой File/New/Web Site..., настроив окно мастера примерно так
  • После щелчка на кнопке OK будет создана начальная страница, которая состоит из двух файлов: Default.aspx и Default.aspx.cs.

    Существует два вида кодировки страниц ASP.NET:

  • Страницы со встроенным кодом (совмещенным), когда весь код C# и HTML-дескрипторы хранятся в одном файле .aspx
  • Страницы со скрытым кодом (раздельным, застраничным), когда код C# хранится отдельно от кода с дескрипторами в файле .aspx.cs
  • Второй способ лучше и начальная страница сайта создается именно так. Большой Билл рекомендует использовать именно второй способ, чтобы разделить дизайн страницы и ее функциональность. Но для тренировки один разок мы воспользуемся первым способом.

  • Через панель
  • В редакторе HTML-дескрипторов скорректируйте декларацию Page, чтобы она стала такой
    <%@ Page Language="C#" %>
  • Сразу под декларацией создайте контейнер <script runat="server"></script> для будущего размещения в нем кода C#. Это можно сделать либо напрямую в режиме Source, либо в режиме Design двойным щелчком на странице создать обработчик события по умолчанию Page_Load(), который оболочка поместит в блок скриптов (а затем за ненадобностью обработчик можно удалить)
  • Полность удалите декларацию DOCTYPE (не обязательно, но чтобы не мешала)
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • Теперь мы получили страницу, где код C# нужно будет размещать в контейнеры <script runat="server"></script>. Этих контейнеров на странице может быть сколько угодно и они могут располагаться в любом месте. ASP.NET перед выполнением всеравно скомпилирует страницу, пройдя весь ее код. Но все же рекомендуется иметь один контейнер и располагать его вначале страницы (для удобства бедного сопровождающего программиста). Начальная заготовка страницы будет выглядеть так

    <%@ Page Language="C#" %>
    	
    <script runat="server">
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
    </html>

    Создание обработчиков

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

    Обработчик загрузки страницы

    Напомним, что каждый раз при поступлении на сервер первого запроса или обратной отсылки ( постинга ) с данными инициируется создание объекта страницы. И как только объект страницы будет загружен в память и будут созданы и инициализированы все элементы страницы, срабатывает событие Load, с которым мы свяжем функцию-обработчик с нужным нам кодом.

    Обычно, при разработке визуальных элементов управления, имеющих события, назначают событие по умолчанию для того, чтобы оболочка при двойном щелчке на элементе в режиме Design автоматичеси создавала обработчик именно этого события. Например, для элемента Button это событие Click, для флажка CheckBox - CheckedChanged, и т.д. Событие по умолчанию назначается с помощью атрибута [DefaultEvent("Имя_события")].

  • Переведите страницу в режим Design и двойным щелчком на ней создайте заготовку обработчика события по умолчанию Load, которое срабатывает каждый раз, когда страница на сервере загружается в память. Теперь в режиме Source страница будет выглядет так
    (рис ) Страница после создания обработчика события Load<%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
    
        }  
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
    </html>
    Заметьте, что в дескрипторе <form> нет атрибутов action и method, это значит, что данные от клиента форма будет посылать этой же странице, и посылать их будет методом post (метод get присоединяет данные элементов управления к URL-адресу). Это нам и нужно, поэтому ничего не будем менять.
  • Поместите с вкладки

    Исходный код страницы Default.aspx будет таким

    <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
        
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Label ID="Label1" runat="server"></asp:Label>
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>
  • Включение режима абсолютного позиционирования элементов управления

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

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

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

  • Переведите страницу .aspx в режим Design, чтобы появилось меню Layout оболочки
  • Выберите команду меню
  • Обработчик переключения экземпляра CheckBox1 элемента CheckBox

  • Вызовите панель
  • Перейдите в панель
  • Центрирование интерфейса

    Отцентрируем наши элементы на странице. Для этого

  • Перейдите на вкладку Source страницы. Установите курсор внутри контейнера

    Обратите внимание, что в меню оболочки для вкладки Source нет пункта Format, который появится только на вкладке Design.

  • Не снимая выделения текста перейдите на вкладку Design.

    Мы видим, что все элементы контейнера <div></div> остались выделенными и появилось меню Format.

  • Отцентрируйте выделенные элементы страницы командой
  • Легко заметить, что эти действия привели к добавлению в дескриптор <div> атрибута внутреннего стиля

    <div style="text-align: center">
    .....................
    </div>

    Это наглядный пример того, что зная где и что подкрутить, можно подкручивать и напрямую. HTML-дескрипторы такие "одноклеточные", что все их поведение "написано у них на лбу". Но чтобы опуститься до их уровня, нам нужно попотеть при подъеме в гору. Серверные элементы из вкладки Standard намного сложнее, зато несравненно функциональней и полностью поддерживают парадигму объектно-ориентированного программирования. Их настройка большей частью выполняется через код C# и панель Properties.

    Стоит упомянуть про режимы редактирования Design и Source. На сервере наша активная страница все равно будет храниться в виде кода, как он представляется на вкладке Source (... и терпеливо ждать своего посетителя). Режим Design является средством визуального проектирования и предназначен для разработчика (нас с вами), чтобы существенно упростить (ускорить) его работу.

    Заполнение обработчиков кодом

    Заполним обработчики. При загрузке странице по первому запросу или обратной отсылке (постинга) мы хотим, чтобы в объект Label1 помещался снимок таймера сервера. Конечно, это не лучший способ спрашивать время у сервера (ходить за семь верст киселя хлебать), когда то же самое можно спросить у таймера клиента. Но пока мы не использовали JavaScript, нам и это сойдет. Кроме этого, в обработчике копируется значение поля объекта TextBox1 в метку Label2 и добавляется восклицание.

    Второй обработчик устанавливает цвет переднего плана и фона метки Label1. Он срабатывает только тогда, когда при обратной отсылке (постинге) обнаружено изменение состояния объекта CheckBox1. Если состояние CheckBox1 между двумя обратными отсылками не изменялось, то события CheckedChanged не возникает и код обработчика не выполняется.

    Полностью код страницы в режиме Source для нашего примера будет таким

    <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = "Текущее время: "
                + DateTime.Now.ToLongTimeString();
            Label2.Text = TextBox1.Text + "!!!";
        }
    	
        protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (CheckBox1.Checked)
            {
                Label1.ForeColor = System.Drawing.Color.White;
                Label1.BackColor = System.Drawing.Color.Red;
            }
            else
            {
                Label1.ForeColor = System.Drawing.Color.Black;
                Label1.BackColor = System.Drawing.Color.White;
            }
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="Label1" runat="server"></asp:Label>
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" OnCheckedChanged="CheckBox1_CheckedChanged" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>
  • Запустите пример, должен получиться внешне приблизительно такой результат
  • Модель событий ASP.NET

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

  • Клиент первый раз запрашивает страницу. Среда исполнения ASP.NET создает объекты страницы и элементов управления, выполняется код инициализации, страница преобразуется в HTML и возвращается клиенту. Объекты страницы сразу же удаляются из памяти сервера, освобождая место для других запросов.
  • Получив HTML-страницу клиент что-то с ней делает и инициирует обратную отсылку - постинг (например, щелкает на кнопке Submit ). Броузер собирает данные со всех элементов формы и события, которые произошли на клиенте, упаковывает все это в пакеты ("пакует чемоданы") и отправляет целевой странице сервера на обработку. Целевая страница указана в атрибуте action HTML-дескриптора формы. Каждый пакет посылается отдельно и с уведомлением успешной доставки.
  • На сервере ASP.NET получает поступившие пакеты, распаковывает и соединяет их. Затем запускает целевую страницу и предает ей полученные данные.
  • Целевая страница загружается в память, повторно создаются объекты с начальной инициализацией как при первой загрузке.
  • Затем серверная страница переводится в состояние, какое она имела перед последней отправкой клиенту. Информацию о последнем состоянии серверной страницы ей передал клиент. Эту информацию он получил от страницы в скрытых полях вместе с последним ответом и теперь вернул в неизменном виде в своем новом запросе (обратной отсылке). Информация о последнем состоянии серверной страницы перед ответом клиенту называется состоянием вида.
  • После этого ASP.NET приступает к анализу поступивших событий и определяет, что клиент хотел сделать с данными перед обратной отсылкой.
  • И наконец, удовлетворяя требования клиента выполняет действия, предписанные событиями в соответствии с имеющимися обработчиками. Тем самым формируется новое состояние серверной страницы и новый HTML-ответ, который отправляется клиенту вместе с новым состоянием вида в скрытых полях.
  • После сгенерированного HTML-ответа страница немедленно выгружается из памяти сервера.
  • Автоматические обратные отсылки

    В нашем примере только кнопка Submit генерирует обратную отсылку на стороне клиента. А любые изменения в элементе TextBox1 или CheckBox1 не будут обработаны до нажатия этой кнопки (фактической отправки формы на серверную обработку). С одной стороны это хорошо, меньше ресурсов тратится на пересылку, но с другой - пользователь не получает немедленный отклик на свои действия.

    В серверных элементах управления такой случай предусмотрен. Если включить свойство AutoPostBack элемента в состояние True, то после завершенных изменений в состоянии элемента автоматически броузером будет инициирована обратная отсылка. Это происходит за счет того, что элемент управления с таким установленным свойством при ответе наряду с HTML генерирует дополнительный скрытый JavaScript -код, заставляющий броузер выполнять обратную отсылку всякий раз, когда происходит изменение состояния этого элемента пользователем.

    Проверим это на нашем предыдущем примере.

  • Выделите на странице Default.aspx объекты TextBox1 и CheckBox1 и в панели Properties установите для них свойство AutoPostBack в значение True. Вы видите, что в дескрипторах этих элементов сразу появился дополнительный атрибут AutoPostBack="True". Теперь при генерировании ответа клиенту эти элементы будут генерировать и JavaScript-код обратной отсылки.
  • Запустите страницу на выполнение и в появившемся окне броузера через контекстное меню выполните опцию View Source. Среди прочего HTML-кода можно увидеть и JavaScript-код обратной отсылки
    ..........................................................
    <script type="text/javascript">
    <!--
    var theForm = document.forms['form1'];
    if (!theForm) {
        theForm = document.form1;
    }
    function __doPostBack(eventTarget, eventArgument) {
        if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
            theForm.__EVENTTARGET.value = eventTarget;
            theForm.__EVENTARGUMENT.value = eventArgument;
            theForm.submit();
        }
    }
    // -->
    </script>
    ..........................................................
        <input id="CheckBox1" type="checkbox" name="CheckBox1" 
        onclick="javascript:setTimeout('__doPostBack(\'CheckBox1\',\'\')', 0)" />
    	
        <input id="TextBox1" type="text" name="TextBox1" value="Сидоров" 
        onchange="javascript:setTimeout('__doPostBack(\'TextBox1\',\'\')', 0)"
        onkeypress="if (WebForm_TextBoxKeyHandler(event) == false) return false;" />
    ..........................................................
    Ах!, какие умные эти серверные элементы: сами сеют - сами жнут - сами песенки поют!!!
  • Поизменяйте состояние элементов в броузере. Теперь на каждые изменения серверу генерируется обратная отсылка (постинг) для обработки. Зачем ждать, как в армии, общей команды Submit...
  • Обратите внимание, что HTML-элементы во вкладке HTML панели Toolbox - слабосильные и автоматическую обратную отсылку не выполняют.

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

    Сохранение состояния вида

    Давайте опять вернемся к нашему простому примеру и выполним следующее:

  • Верните свойство AutoPostBack элементов CheckBox1 и TextBox1 опять в состояние False
  • Выделите элемент Label1 и установите его свойство EnableViewState в состояние False (по умолчанию оно установлено в значение True ). Немедленно в дескрипторе элемента появится атрибут EnableViewState="False"
  • Запустите приложение и отметьте, что цвет элемента Label1 (Текущее время:...) меняется только при первой отсылке после изменения элемента CheckBox1. Последующие отсылки восстанавливают начальный цвет.
  • Что здесь происходит, почему цвет метки выполняет только первое новое указание элемента CheckBox1... Все дело в протоколе HTTP и во времени жизни страницы. HTTP -протокол не хочет сохранять последнее состояние страницы, считая очередной запрос к странице первым, а сама страница существует только на время обработки запроса и затем немедленно выгружается, чтобы освободить ресурсы сервера для работы других страниц.

    Наша метка Label1 обновляет цвет только тогда, когда срабатывает обработчик изменения состояния элемента CheckBox1. А это происходит только тогда, когда броузер пришлет информацию, что произошло событие изменения состояния элемента CheckBox1.

    Допустим, что пользователь поставил галочку в CheckBox1. Броузер прислал это событие на страницу. ASP.NET это событие распознала, запустила наш обработчик изменения цвета и в соответствии с новым состоянием CheckBox1 (checked) выполнила код

    Label1.ForeColor = System.Drawing.Color.White;
    Label1.BackColor = System.Drawing.Color.Red;

    Этим кодом объект Label1 будет настроен и сгенерирует броузеру соответствующий HTML-код цветной метки. Но при следующей обратной отсылке, если оставить объект CheckBox1 в том же неизменном состоянии checked, сведений о событии изменения его состояния сервер не получит. Поэтому и делать с элементом Label1 при его создании сервер ничего не будет, а сгенерирует его начальное состояние, установленное в панели Properties. Что пользователь в результате получит от сервера - начальные установки цвета Label1. У него стоит галочка цветности метки, а она присылается совсем с другим, начальным, цветом.

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

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

    <input type="hidden" 
    	       name="__VIEWSTATE" id="__VIEWSTATE" 
    	       value="/wEPDwULLTE2ODAxODI5OTYPZBYCAgMPZBYEAgMPEA
    	              8WAh4HQ2hlY2tlZGdkZGRkAgcPDxYCHgRUZXh0BRHQodC90LXRgtC6
    	              0L7QsiEhIWRkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZX
    	              lfXxYBBQlDaGVja0JveDEKD82+p/Oho3OWiA2YZad613t4ZA==" />

    Атрибут value скрытого поля hidden имеет значение упакованного состояния элементов управления, которое вместе с ответом передается на временное хранение броузеру.

  • Восстановите механизм сохранения состояния вида элементу Label1 нашей страницы, вернув свойство EnableViewState в значение True. Проверьте работу элемента Label1, запустив страницу. Теперь все работает как надо - цветность метки сохраняется при каждом ответе сервера!
  • Отметьте для себя, что серверные элементы сохраняют интерфейсные значения даже при выключенном механизме сохранения состояния вида. Про несерверные (нет атрибута runat="server" ) HTML-элементы такого не скажешь. Web-элементы с вкладки Standard - все серверные.

    Для примера добавьте на страницу (в любое место) из вкладки HTML элемент Input (Checkbox). Выполните страницу, измените состояние элемента и отправьте страницу на сервер. Сервер вернет отклик с первоначальным состоянием элемента. Если же превратить элемент Input (Checkbox) в серверный командой Run As Server Control, то элемент при постинге будет сохранять свое состояние.

    Этапы выполнения страниц на сервере

    Со стороны сервера обработка Web-формы (страницы) средой ASP.NET происходит поэтапно. На каждом этапе генерируются соответствующие события, которые наш код может перехватывать и выдавать нужный ответ. Поток обработки страницы проходит следующие этапы:

  • Инициализация структуры страницы
  • Инициализация кода пользователя
  • Проверка достоверности данных
  • Обработка событий
  • Автоматическая привязка данных
  • Очистка
  • Графически это можно изобразить так

    Инициализация структуры страницы

    На этом этапе страница загружается в память, расшифровывается информация о состоянии вида (если это обратная отсылка), запускается событие Page.Init. Но это событие редко обрабатывается разработчиком, поскольку объекты элементов управления еще не созданы, а информация о состоянии вида еще полностью не расшифрована.

    Инициализация кода пользователя

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

    Для определения состояния страницы можно использовать свойство Page.IsPostBack, которое будет иметь значение false при первом запросе страницы. IsPostBack является статическим свойством класса Page. Оно всегда возвращает информацию, основанную на текущей странице. Можно также использовать свойство экземпляра как this.IsPostBack, что равносильно Page.IsPostBack.

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

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

    В тех случаях, когда действительно легче инициализировать элемент управления в коде, лучше отключить состояние вида для этого элемента значением EnableViewState="false" и инициализировать элемент каждый раз при запуске события Page.Load независимо от того, является ли текущий запрос обратной отсылкой или поступил впервые. В качестве упражнения выполним программное восстановление состояния метки в зависимости от состояния элемента управления CheckBox1 нашего примера

  • Выделите элемент управления Label1 и через панель Properties установите для него EnableViewState="false"
  • Включите флажок CheckBox1 и многократно выполните обратную отсылку, чтобы убедиться, что состояние вида Label1 не сохраняется
  • Дополните блок скриптов страницы Default.aspx кодом явного вызова обработчика CheckBox1_CheckedChanged()
  • <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = "Текущее время: "
                + DateTime.Now.ToLongTimeString();
            Label2.Text = TextBox1.Text + "!!!";
                
            // Восстанавливаем Label1 программно место состояния вида   
            CheckBox1_CheckedChanged(null, EventArgs.Empty); 
        }
    	
        protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (CheckBox1.Checked)
            {
                Label1.ForeColor = System.Drawing.Color.White;
                Label1.BackColor = System.Drawing.Color.Red;
            }
            else
            {
                Label1.ForeColor = System.Drawing.Color.Black;
                Label1.BackColor = System.Drawing.Color.White;
            }
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="Label1" runat="server" EnableViewState="false" />
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" OnCheckedChanged="CheckBox1_CheckedChanged" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>

    Теперь мы видим, что элемент Label1 работает правильно, восстанавливая свое состояние программно в зависимости от состояния флажка.

    Проверка достоверности

    При формировании обратной отсылки броузер собирает с элементов дескриптора <form> значения полей и состояния. Он не проверяет соответствие типов собираемых данных ожидаемым значениям. Например, если текстовое поле ожидает цифры, а пользователь ввел туда буквы, значение поля нормально будет передано на сервер. Но серверный код ожидает цифры и готов применить к ним математические операции.

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

  • Запустите страницу Default.aspx и введите в поле TextBox1 какой-нибудь текст в угловых скобках
  • Сервер немедленно прервет выполнение страницы и выдаст сообщение Server Error in '/WebSite1' Application с подробным анализом ошибки.

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

    Но и серверная сторона также проверяет достоверность поступивших данных, прежде чем начать их обработку. В ASP.NET предусмотрены специальные классы ( валидаторы ), которые выполняют проверку достоверности для закрепленных за ними элементов управления. Но существует общий способ проверить достоверность всей страницы сразу. Достаточно в каком-нибудь обработчике проверить значение свойства страницы Page.IsValid.

    Обработка событий

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

  • События, требующие немедленного ответа сервера (щелчок на кнопке Submit, изменение состояния элемента управления с установленным свойством AutoPostBack, и др.). Эти события инициируют немедленную обратную отсылку.
  • Отложенные события изменения состояния - включают смену выбора в элементе управления или изменение текста в текстовом поле, но обнаруживаемые ASP.NET только по прибытию очередной обратной отсылки.
  • Такая модель событий ASP.NET отличается от традиционных локальных Windows-приложений. В Windows-приложении состояние формы (окна) целиком находится в памяти и способно немедленно отреагировать на происшедшее событие. В Web-приложении обработка событий формы (страницы) происходит только при поступлении обратной отсылки на сервер. В связи с этим на сервер поступает сразу несколько необработанных событий и важно знать, в какой последовательности они обрабатываются.

    Например, пусть у нас есть текстовое поле без автоматической обратной отсылки и кнопка Submit. Мы изменяем текстовое поле и щелкаем на кнопке Submit, требуя от броузера обратную отсылку. Запрос поступает на сервер, анализируется средой исполнения ASP.NET, которая запускает следующие события в таком порядке:

  • Page.Init
  • Page.Load
  • TextBox.TextChanged
  • Button.Click
  • Page.PreRender
  • Page.Unload
  • Запоминание этой информации может быть решающим в облегчении нашей жизни как программистов ASP.NET. Нельзя подсовывать сырые заготовки на выходе конвейера готовой продукции. Это также важно, как помнить о том, что страница существует только на время формирования очередного ответа клиенту и никакие переменные приложения не сохраняют своих значений до следующего поступления обратной отсылки. В этом состоит отличительная особенность Web-приложений.

    Автоматическая привязка данных

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

    Существуют два типа операций с источниками данных. Любые изменения (вставка, удаление, изменение записей БД) выполняются после обработки всех событий элементов управления, но непосредственно перед генерацией события Page.PreRender. После запуска события Page.PreRender наступает черед элементов управления источниками данных, которые выполняют свои запросы и вставляют извлеченные данные в связанные с этими данными элементы управления. При этом ни один обработчик событий элементов отображения уже не будет иметь доступа к новым данным.

    Это последняя остановка в жизненном цикле страницы. Исторически сложилось так, что событие Page.PreRender обозначает последнее действие перед преобразованием страницы в HTML.

    Очистка

    В конце своего жизненного цикла страница преобразуется в HTML. После этого начинается реальная очистка и запускается событие Page.Unload. В этот момент объекты страницы все еще доступны, но окончательный HTML уже сгенерирован и не может быть изменен. Затем система теряет ссылку на объект страницы и сборщик мусора уничтожает брошенную память. Последним предсмертным писком страницы становится событие Page.Disposed, когда сборщик мусора доберется до нее. С получением сервером очередного запроса страницы все начинается заново.

    Пример страничного потока событий

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

    Добавление к сайту новой страницы

  • Выполните опцию меню оболочки Website/Add New Item... или такую же опцию в контекстном меню узла сайта панели Solution Explorer
  • Заполните окно мастера добавления как показано на снимке. Имя странице присвойте
  • Выполните команду Website/Set As Start Page (сделать страницу стартовой), чтобы при запуске проекта из оболочки она выполнялась первой. Такая установка применяется только в среде разработки и не влияет на порядок загрузки страниц на промышленном сервере.
  • Поместите на страницу из вкладки Standard панели Toolbox серверный элемент управления Label и Button. Отцентрируйте их в режиме Design командой Format/Justify/Center
  • Переименуйте элемент Label1 в lblInfo
  • Через панель Document Outline в режиме Design выделите объект lblInfo и в панели Properties установите свойство EnableViewState в состояние false
  • Отметьте для себя, что установленные отличными по умолчанию значения свойств элементов управления выделяются в поле значений панели Properties полужирным стилем шрифта.

  • Очистите значение свойства Text элемента lblInfo
  • Двойным щелчком на странице в режиме Design создайте заготовку обработчика Page_Load() и растиражируйте ее редактором кода еще на три обработчика
  • Двойным щелчком на кнопке Button1 в режиме Design создайте для нее заготовку обработчика Button1_Click()
  • Заполните обработчики следующим кодом
    (рис ) Файл отделенного кода PageFlow.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
        
    public partial class PageFlow : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Объекты созданы и инициализированы начальными значениями
            // и возвращенным состоянием вида
            lblInfo.Text += "Сработал обработчик события Load <br />";
    	
            if (Page.IsPostBack)
            {
                lblInfo.Text += "<b>Эта страница запрашивается не впервые</b><br />";
            }
        }
    	
        protected void Page_Init(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события Init <br />";       
        }
    	
        protected void Page_PreRender(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события PreRender <br /><hr />";       
        }
    	
        protected void Page_Unload(object sender, EventArgs e)
        {
            // Не увидим, HTML уже сгенерирован
            lblInfo.Text += "Сработал обработчик события Unload <br />";       
        }
    	
        protected void Button1_Click(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события Click кнопки <br />";       
        }
    }
    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="PageFlow.aspx.cs" Inherits="PageFlow" %>
    	
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="lblInfo" runat="server" EnableViewState="False"></asp:Label>
                <br />
                <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
            </div>
        </form>
    </body>
    </html>
  • Запустите приложение первый раз. Сервер сгенерирует следующий HTML-ответ
  • Щелкните на кнопке
  • Обратите внимание на файл поддержки PageFlow.aspx.cs. Он не полон - в производном классе страницы нет объявлений объектов lblInfo и Button1 элементов управления. Такое в языке C# недопустимо. Однако страница нормально компилируется и успешно выполняется. Дело все в ключевом слове partial (частичный) в объявлении класса. Это нововведение в Visual Studio 2005. Оболочка сама перед компиляцией файла добавляет необходимые объявления объектов на основании информации о дескрипторах, связанных с серверными элементами на основной странице .aspx. Это справедливо, поскольку на странице со смешанным кодом и раньше не было никаких объявлений (как и не было классов).

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

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

    Страница как контейнер элементов управления

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

    Каждый раз, когда страница создается в ответ на HTTP-запрос, система ASP.NET инспектирует файл .aspx. Для каждого найденного дескриптора элемента управления она создает и настраивает объект элемента управления, а затем добавляет этот объект к странице как ее дочерний элемент управления. Ссылки на все дочерние элементы страницы размещаются в коллекции Page.Controls.

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

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

  • Добавьте к проекту новую страницу с именем ControlTree.aspx с раздельным кодом в файле ControlTree.aspx.cs.
  • Пометьте ее как стартовую
  • Поместите на страницу элемент управления Panel из вкладки Standard панели Toolbox
  • Поместите на панель три кнопки Button, одну метку Label и текстовое поле TextBox
  • Разместите за пределами панели, но внутри дескриптора <form>, внизу еще одну кнопку Button
  • Введите сверху и снизу за пределами дескриптора <form> статический HTML-текст внутри дескрипторов параграфа <p></p>
  • Код разметки для полученной страницы должен выглядеть примерно так

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="ControlTree.aspx.cs" 
        Inherits="ControlTree" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Дерево элементов управления</title>
    </head>
    <body>
        <p>
            <i>Это статический текст 1</i></p>
        <form id="Controls" method="post" runat="server">
            <div>
                <asp:Panel ID="MainPanel" runat="server" Height="121px" Width="267px">
                    <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 />
                    <asp:Label ID="Label1" runat="server" Text="Label">Name:</asp:Label>
                    <asp:TextBox ID="TextBox1" runat="server" Width="195px"></asp:TextBox>
                </asp:Panel>
                <asp:Button ID="Button4" runat="server" Text="Button4" />
            </div>
        </form>
        <p>
            <i>Это статический текст 2</i></p>
    </body>
    </html>

    Представление этого кода разметки в окне броузера имеет такой вид

  • Установите для кнопки Button4 свойство ToolTip (всплывающая подсказка) в состояние Сканировать все
  • Создайте обработчик кнопки Button4
  • Заполните кодовый файл так
    (рис ) Код файла ControlTree.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
        
    public partial class ControlTree : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Начало сканирования всех нелитеральных элементов управления
            DisplayControl(Page.Controls, 0, false);
    	
            // Горизонтальная линия по окончании вывода
            Response.Write("<hr />");
        }
    	
        protected void Button4_Click(object sender, EventArgs e)
        {
            // Начало сканирования всех литеральных элементов управления
            DisplayControl(Page.Controls, 0, true);
    	
            // Горизонтальная линия по окончании вывода
            Response.Write("<hr />");
        }
    	
        private void DisplayControl(ControlCollection controls, int depth, bool literalFlag)
        {
            foreach (Control control in controls)
            {
                // Выявлять литеральные (статические) элементы управления
                bool literalControl = control is LiteralControl;
    	
                // Отобразить элемент управления
                if (literalControl == literalFlag)
                {
                    // Использовать параметр depth для определения
                    // отступов в выводе дерева элементов управления
                    Response.Write(new String('-', depth * 4) + "-->");
    	
                    String literalText = "";
                    if (literalControl)
                        literalText = "text" + ((LiteralControl)control).Text;
    	
                    Response.Write(control.GetType().ToString() + " - <b>"
                        + control.ID + literalText + "</b><br />");
                }
    	
                // Проверять дочерние элементы управления рекурсивно
                if (control.Controls != null)
                {
                    DisplayControl(control.Controls, depth + 1, literalFlag);
                }
            }
        }
    }
  • Запустите страницу. После щелчка на кнопке Button4 должен получиться примерно такой HTML-выход
    -->System.Web.UI.HtmlControls.HtmlHead - 
    ------>System.Web.UI.HtmlControls.HtmlTitle - 
    -->System.Web.UI.HtmlControls.HtmlForm - Controls
    ------>System.Web.UI.WebControls.Panel - MainPanel
    ---------->System.Web.UI.WebControls.Button - Button1
    ---------->System.Web.UI.WebControls.Button - Button2
    ---------->System.Web.UI.WebControls.Button - Button3
    ---------->System.Web.UI.WebControls.Label - Label1
    ---------->System.Web.UI.WebControls.TextBox - TextBox1
    ------>System.Web.UI.WebControls.Button - Button4
    -->System.Web.UI.LiteralControl - text 
    -->System.Web.UI.LiteralControl - text 
    
    Это статический текст 1
    
    
    ------>System.Web.UI.LiteralControl - text
    
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ------>System.Web.UI.LiteralControl - text 
    ------>System.Web.UI.LiteralControl - text 
    -->System.Web.UI.LiteralControl - text 
    
    Это статический текст 2
  • Объекты LiteralControl, которые мы определяли в нашем коде и отдельно распечатывали по кнопке Button4, предоставляют немногое в отношении функциональности. Например, мы не можем программно устанавливать информацию, связанную со стилями, наподобие цвета или шрифта. У них также нет уникальных серверных идентификаторов. Однако мы можем манипулировать содержимым LiteralControl с использованием его свойства Text, что и продемонстрировано в последнем примере.

    Здесь при рекурсивном обходе дерева элементов управления мы учитывали тот факт, что ASP.NET генерирует страницу иерархически. Она генерирует непосредственно только верхний уровень элементов управления, т.е. только свойство-контейнер ( коллекцию элементов управления ) Controls самой страницы. Если какой-то элемент управления содержит дочерние элементы, т.е. сам является контейнером, то он имеет свое собственное свойство-контейнер Controls.

    Обратите внимание, что задав свойство ToolTip конопки Button4, мы организовали для пользователя появление всплывающей подсказки при наведении курсора мыши на кнопку. Фактически серверный элемент Button4 сгенерировал в своем HTML-выводе атрибут title="Сканировать все".

    Программное управление заголовком страницы

    Ранее мы говорили, что чтобы сделать дескриптор HTML элементом управления, необходимо добавить в него атрибут runat="server". Но в сканированном выводе последнего примера система ASP.NET нашла элемент управления заголовком страницы

    System.Web.UI.HtmlControls.HtmlHead

    Visual Studio 2005 автоматически считает дескриптор <head> серверным элементом. Значит мы можем управлять им программно. Объект HtmlHead наследует предопределенный интерфейс IPageHeader и содержит следующие свойства:

  • Title - название HTML-страницы, которое отображается в заголовке броузера
  • LinkedStyleSheets - коллекция объектов для настройки стилей всего документа через внешнюю таблицу стилей
  • StyleSheet - настройка встраиваемых стилей
  • Metadata - коллекция объектов для настройки дескрипторов метаданных
  • Пример:

    Page.Header.Title = "Это программно заданный заголовок";
    Page.Header.Metadata.Add("Keywords", "Рыжик, Пыжик, ГдеТыБыл");
    Page.Header.Metadate.Add(Description", "Русские народные мелодии");

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

    Пример создания элемента управления во время выполнения

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

  • Добавьте к проекту предыдущего примера новую страницу с разделенным кодом и именем DynamicButton. Сделайте страницу стартовой.
  • Поместите на страницу из вкладки Standard элемент управления Panel, метку Label, три кнопки Button, а из вкладки HTML - элемент Horizontal Rule
  • Настройте интерфейс страницы времени проектирования так

  • Заполните обработчик загрузки страницы следующим кодом
    (рис ) Начальный код динамического создания кнопкиpublic partial class DynamicButton : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать новый объект кнопки
            Button newButton = new Button();
    	
            // Присвоить значения свойств
            newButton.ID = "newButton";// Пометили
            newButton.Text = "* Dynamic Button *";
    	
            // Добавить кнопку в коллекцию элементов управления формы
            form1.Controls.Add(newButton);
        }
    }
  • Запустите страницу на выполнение. Получится такой результат
  • Мы получили то, что динамически созданная кнопка оказалась в конце декскриптора <form>. Для получения большего контроля над расположением динамического элемента управления можно использовать элемент управления PlaceHolder. Целью элемента управления PlaceHolder (держатель места) является размещение других элементов управления. Если разместить элемент PlaceHolder на форме и не добавить в его коллекцию Controls других элементов, он ничего не изменит на окончательном HTML-выводе.

  • Поместите элемент управления PlaceHolder впереди кнопки с названием "Reset Text" и перепишите код так, чтобы динамически создаваемая кнопка добавлялась не в коллекцию формы, а в коллекцию PlaceHolder
    // Добавить кнопку в коллекцию PlaceHolder1
    PlaceHolder1.Controls.Add(newButton);
  • Теперь HTML-вывод должен быть таким

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

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

    Если мы собираемся взаимодействовать с динамически созданным элементом управления дальше, то ему следует присвоить уникальный идентификатор, что мы и сделали в нашем примере. Мы можем использовать этот идентификатор для извлечения элемента управления из коллекции Controls родительского контейнера. Можно найти элемент управления с использованием рекурсивной логики поиска, как мы это делали в странице ControlTree.aspx. Можно для поиска элемента использовать статический метод Page.FindControl().

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

  • Двойным щелчком на кнопке с названием Remove Button создайте для нее обработчик, который заполните так
    (рис ) Обработчик события кнопки Remove Button в файле DynamicButton.aspx.csprotected void Button3_Click(object sender, EventArgs e)
    {
        // Поиск кнопки вне зависимости от иерархического
        // уровня, в котором она расположена
        Button foundButton = (Button)Page.FindControl("newButton");
    	
        // Удаление кнопки с проверкой условия, чтобы
        // не пытались удалить уже удаленный объект
        if (foundButton != null)
        {
            // Удаляем из коллекции родительского элемента управления
            foundButton.Parent.Controls.Remove(foundButton);
        }
    }
  • Следует заметить, что последний оператор приведенного кода Remove() не удаляет объект кнопки физически, а удаляет только ссылку на кнопку из коллекции PlaceHolder. Это значит, что кнопка, однажды созданная в обработчике Page_Load(), продолжает существовать в контейнере Page страницы и мы всегда можем ее использовать дальше.

    Динамически добавленные элементы управления могут обрабатывать события. Нужно лишь подключить обработчик события с использованием кода-делегата. Подключать события обязательно нужно в обработчике Page_Load(), иначе будет поздно, поскольку все поступившие с обратной отсылкой события клиента генерируются сразу после события Page.Load. Если код подключения события с помощью делегата разместить в другом месте, обработчик события не сработает, поскольку система это событие уже сгенерирует и, не найдя соответствующий обработчик, оставит без внимания.

  • Добавьте в конец обработчика Page_Load() следующий код
    (рис ) Подключение обработчика события динамически созданного элемента управленияprotected void Page_Load(object sender, EventArgs e)
    {
        // Создать новый объект кнопки
        Button newButton = new Button();
    	
        // Присвоить значения свойств
        newButton.ID = "newButton";
        newButton.Text = "* Dynamic Button *";
    	
        // Добавить кнопку в коллекцию PlaceHolder1
        PlaceHolder1.Controls.Add(newButton);
    	
        // Подключение обработчика события Button.Click
        // динамически созданного элемента управления
        newButton.Click += new EventHandler(newButton_Click);
    }
    	
    void newButton_Click(object sender, EventArgs e)
    {
        Label1.Text = "Этот текст прислал обработчик" + 
                      " динамически созданной кнопки";
    }
  • Сбросьте для метки Label1 свойство сохранения состояния вида, назначив EnableViewState=false
  • Окончательный вариант страницы должен быть таким

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="DynamicButton.aspx.cs" Inherits="DynamicButton" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:Label ID="Label1" runat="server" Text="Щелкните на динамически созданной кнопке"
                Width="431px" EnableViewState="False"></asp:Label><br />
            <br />
            <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            nbsp;
            <asp:Button ID="Button1" runat="server" Text="Reset Text" /><br />
            <hr />
            <asp:Button ID="Button2" runat="server" Text="Create Button" />
            nbsp;nbsp;
            <asp:Button ID="Button3" runat="server" Text="Remove Button" OnClick="Button3_Click" />
        </div>
        </form>
    </body>
    </html>
    using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class DynamicButton : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать новый объект кнопки
            Button newButton = new Button();
    	
            // Присвоить значения свойств
            newButton.ID = "newButton";
            newButton.Text = "* Dynamic Button *";
    	
            // Добавить кнопку в коллекцию PlaceHolder1
            PlaceHolder1.Controls.Add(newButton);
    	
            // Подключение обработчика события Button.Click
            // динамически созданного элемента управления
            newButton.Click += new EventHandler(newButton_Click);
        }
    	
        void newButton_Click(object sender, EventArgs e)
        {
            Label1.Text = "Этот текст прислал обработчик" + 
                          " динамически созданной кнопки";
        }
    	
        protected void Button3_Click(object sender, EventArgs e)
        {
            // Поиск кнопки вне зависимости от иерархического
            // уровня, в котором она расположена
            Button foundButton = (Button)Page.FindControl("newButton");
    	
            // Удаление кнопки с проверкой условия, чтобы
            // не пытались удалить уже удаленный объект
            if (foundButton != null)
            {
                // Удаляем из коллекции родительского элемента управления
                foundButton.Parent.Controls.Remove(foundButton);
            }
        }
    }
  • Запустите и испытайте закодированную страницу DynamicButton.aspx
  • В окне броузера мы видим, что при каждом вызове страницы динамическая кнопка создается на сервере и посылается клиенту. При нажатии кнопки Remove Button посланное на сервер событие вызывает срабатывание обработчика удаления кнопки из коллекции. Посылка данных на сервер при нажатии других незапрограммированных кнопок опять создает динамическую кнопку и добавляет ее в контейнер. Щелчок на динамической кнопке запускает обработчик смены текста в метке Label1. Щелчок на любой другой кнопке восстанавливает первоначальное содержимое метки Label1, поскольку мы сняли свойство сохранения состояния вида.

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

    Класс Page

    Класс System.Web.UI.Page является базовым для классов создаваемых страниц. В локальных приложениях такую роль играет класс Form. Это значит, что динамические страницы наследуют множество полезных свойств и методов класса Page. Класс Page служит контейнером имен всех элементов управления, входящих в состав страницы. ASP.NET создает новое виртуальное пространство имен, гарантирующее всем дочерним элементам управления уникальность имен в пределах всего дерева элементов управления.

    Свойства класса Page можно разделить на три группы:

  • Внутренние объекты
  • Рабочие свойства
  • Специфические страничные свойства
  • На данном этапе мы коснемся только первой группы свойств.

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

    Внутренние объекты ASP.NET в составе класса Page
    Свойство-объект Порождающий класс Описание
    Application System.Web.HttpApplicationState Содержит информацию о состоянии приложения
    Cache System.Web.Caching.Cache Следит за приоритетностью и устареванием элементов
    Request System.Web.HttpRequest Содержит текущий запрос HTTP
    Response System.Web.HttpResponse Осуществляет отправку ответа клиенту
    Server System.Web.HttpServerUtility Предоставляет вспомогательные методы для отправки Web-запросов
    Session System.Web.SessionState.HttpSessionState Управляет данными, связанными с определенным пользователем
    Trace System.Web.TraceContext Осуществляет трассировку выполнения страницы
    User System.Security.Principal.IPrincipal Представляет пользователя, от которого поступил запрос

    Объекты Session, Application, Cache

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

    Объект Application содержит словарь данных имя=значение, глобальных для всего приложения.

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

    Объект Request

    Этот объект представляет значения и свойства HTTP -запроса, вызвавшего загрузку страницы. Он содержит все параметры URL и другую информацию, отправляемую клиентом. Мы можем использовать объект Request, чтобы обнаружить, какой броузер используется клиентом, или устанавливать и анализировать cookie -наборы ( куки ). Ниже приведены наиболее распространенные свойства класса System.Web.HttpRequest, порождающего объект Request

    Свойства класса System.Web.HttpRequest
    Свойство Порождающий класс Описание
    ApplicationPath string Виртуальный каталог приложения ASP.NET
    PhysicalPath string Реальный каталог приложения ASP.NET
    AnonymousID string Однозначно идентифицирует текущего пользователя при включенном анонимном доступе
    Browser System.Web.HttpBrowserCapabilities Содержит свойства, которые описывают различные возможности броузера пользователя. Например, поддержку элементов управления ActiveX, cookie -наборов, активных сценариев, фреймов и т.д.
    ClientCertificate System.Web.HttpClientCertificate Предоставляет сертификат безопасности для текущего запроса, если таковой существует
    Cookies System.Web.HttpCookieCollection Предоставляет коллекцию cookie -наборов, прибывших с текущим запросом (постингом)
    FilePath string Возвращает реальный путь (относительно сервера) к файлу страницы, запустившей процесс выполнения
    CurrentExecutionFilePath string Содержит реальный путь новой страницы, если мы программно переместили пользователя на новую страницу с помощью метода Server.Transfer() без его уведомления (без полного цикла). Иначе содержит то же, что и FilePath
    Form System.Collections.Specialized.NameValueCollection Представляет коллекцию переменных формы, поступивших с обратной отсылкой (постингом). В большинстве случаев мы будем извлекать эту информацию из свойств элемента управления вместо использования этого набора
    Headers System.Collections.Specialized.NameValueCollection Предоставляет коллекцию HTTP-заголовков имя=значение
    ServerVariables System.Collections.Specialized.NameValueCollection Предоставляет коллекцию глобальных серверных переменных имя=значение
    IsAuthenticated bool Возвращает true при успешной аутентификации пользователя
    IsSecureConnection bool Возвращает true при успешном подключении пользователя по протоколу защищенных сокетов (Secure Sockets Layer - SSL)
    IsLocal bool Возвращает true при запросе пользователем страницы с текущего компьютера
    QueryString System.Collections.Specialized.NameValueCollection Предоставляет параметры, переданные клиентом в строке запроса методом get
    Url System.Uri Содержит URL-адрес текущей страницы
    UrlReferrer System.Uri Содержит URL-адрес страницы, с которой пользователь попал на текущую
    UserAgent string Строка, представляющая тип броузера. Для этого свойства в Microsoft Internet Explorer предусмотрено значение MSIE
    UserHostAddress string Предоставляет IP-адрес клиента
    UserHostName string Предоставляет доменное имя клиента (DNS - Domain Name System - служба имен доменов)
    UserLanguages string Предоставляет отсортированный массив, который перечисляет языковые предпочтения клиента. Может пригодиться при создании многоязычных страниц

    Объект Response

    Этот объект содержит информацию и методы формирования ответа Web-сервера на запрос клиента. В таблице перечислены основные свойства и методы объекта Response.

    Основные свойства и методы объекта Response
    Член Тип Описание
    BufferOutput bool При установке в true (по умолчанию) страница не отправляется клиенту до тех пор, пока не будет полностью сгенерирована (в отличие от отправки по частям при false )
    Cache System.Web.HttpCachePolicy Позволяет конфигурировать кэширование вывода
    Cookies System.Web.HttpCookieCollection Содержит коллекцию cookie-наборов, передаваемых вместе с ответом. Можно использовать для добавления дополнительных cookie -наборов
    Expires int Это свойство можно использовать для кэширования сгенерированного HTML для страницы, что улучшает производительность последующих запросов
    ExpiresAbsolute System.DateTime Это свойство можно использовать для кэширования сгенерированного HTML для страницы, что улучшает производительность последующих запросов
    IsClientConnected bool Указывает на то, подключен ли клиент к серверу. Если нет, можно потребовать остановить длинную операцию
    Write(char);

    Write(char[ ], int, int);

    Write(object);

    Write(string)

    Эти методы позволяют записать текст содержимого соответствующего типа непосредственно в поток ответа. Можно даже записать содержимое файла. Эти методы не так важны и не должны использоваться в сочетании с серверными элементами управления, которые сами заботятся о выводе
    BinaryWrite(byte[ ]) Эти методы позволяют записать текст содержимого соответствующего типа непосредственно в поток ответа. Можно даже записать содержимое файла. Эти методы не так важны и не должны использоваться в сочетании с серверными элементами управления, которые сами заботятся о выводе
    WriteFile(IntPtr, long, long);

    WriteFile(string);

    WriteFile(string, bool);

    WriteFile(string, long, long)

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

    Redirect(string, bool)

    Этот метод направляет пользователя на другую страницу приложения или на другой сайт. Этот метод требует полного цикла с уведомлением пользователя. По сути он отправляет сообщение броузеру, которое заставляет его запросить новую страницу. Метод Server.Transfer() сразу загружает новую страницу и начинает ее обработку. В результате URL, отображаемый в броузере клиента, не меняется. Но этим методом перемещаться на другой сайт нельзя

    Пример:

    // Перенаправляем на файл в текущем каталоге
    Response.Redirect("newpage.aspx");
    .............................
    // Перенаправляем на другой Web-сайт
    Response.Redirect("http://www.dialog-service.net");

    Объект Server

    Основные свойства и методы объекта Server
    Член Тип Описание
    MachineName string Представляет имя компьютера, на котором запускается страница. Это имя Web-сервера, используемое компьютером с целью его идентификации для остальной части сети
    CreateObject(string);

    CreateObject(System.Type)

    Создает экземпляр COM-объекта, определяемый его программным идентификатором progID. Используется для обратной совместимости, поскольку упрощает взаимодействие с COM-объектами в .NET
    GetLastError() Извлекает объект самого последнего исключения или нулевую ссылку, если исключения не было. Используется в обработчике событий приложения, проверяющего сбойные ситуации.
    HtmlEncode(string);

    HtmlEncode(string, System.IO.TextWriter)

    Осуществляет HTML-кодирование строки допустимыми символами, которые не будут восприниматься как управляющие ( < lt; > gt; amp; жесткий пробел nbsp )
    HtmlDecode(string);

    HtmlDecode(string, System.IO.TextWriter)

    Осуществляет обратную операцию по отношению к HTML-кодированной строке символов
    UrlEncode(string);

    UrlEncode(string, System.IO.TextWriter)

    Заменяет обычную строку строкой допустимых символов URL, отменяя пробелы и другие спецсимволы, кодируя кириллицу, %, ?,
    UrlDecode(string);

    UrlDecode(string, System.IO.TextWriter)

    Раскодирует строку символов URL
    UrlTokenEncode(byte[ ]) Выполняет те же действия, что и UrlEncode(), но работает с байтовым массивом, который содержит данные в формате Base64
    UrlTokenDecode(string) Выполняет те же действия, что и UrlDecode(), но работает с байтовым массивом, который содержит данные в формате Base64
    MapPath(string) Возвращает физический файл, соответствующий определенному виртуальному пути файла на Web-сервере
    Transfer(string);

    Transfer(string, bool);

    Transfer(System.Web.IHttpHandler, bool)

    Передает исполнение другой Web-странице в текущем приложении. Этот метод похож на метод Response.Redirect(), но работает быстрее. Но его нельзя применять для перемещения пользователя на сайт, расположенный на другом Web-сервере, или же на страницу, отличную от ASP.NET (например, HTML или ASP)

    При использовании метода Server.Transfer() не происходит полного цикла. Вместо этого механизм ASP.NET просто загружает новую страницу и начинает ее обработку. В результате URL, отображаемый в броузере клиента, не меняется (...хотели кока, а съели Кука).

    Например:

    // Можно переместиться на файл в текущем Web-приложении
    Server.Transfer("newpage.aspx");
    ................................
    // Перемещаться на другой Web-сайт нельзя.
    // Это вызовет ошибку!!!
    Server.Transfer("http://www.prosetech.com");

    Метод Server.MapPath() является еще одним полезным методом. Пусть, например, мы собираемся загрузить файл под названием info.txt из текущего виртуального каталога. Вместо жесткого кодирования пути можно использовать Request.ApplicationPath(), чтобы получить текущий относительный виртуальный каталог. Затем применить метод Server.MapPath() - для преобразования его в абсолютный физический путь.

    Например:

    string physicalPath = Server.MapPath(Request.ApplicationPath 
    						+ "/info.txt");
    	
    // Открытие файла
    StreamReader reader = new StreamReader(physicalPath);
    // Что-то делаем
    reader.Close();

    Объект User

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

    Страницы:
    Файлы к лекции Вы можете скачать здесь

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

    В основе Web-форм HTML-страниц лежит дескриптор <form>...</form>. В активных страницах других систем, например PHP, страница допускает наличие на ней нескольких дескрипторов <form>...</form> (дескрипторов HTML-форм). В таком случае эти дескрипторы должны иметь атрибуты ID с разными значениями, чтобы поддерживающая активные страницы система могла различать, какая форма прислала на сервер данные, которые нужно обработать.

    Страницы ASP.NET требуют, чтобы на каждой из них было не более одной формы (в принципе, форм на странице может быть несколько, но в каждый момент видимой Visible=True может быть только одна форма).

    Но этого достаточно и для обычной HTML-страницы, дабы сколько бы элементов управления HTML-форма не содержала, с них всех собирается информация при подготовке обратной отсылки на сервер. Когда пользователь щелкает на кнопке Submit, броузер извлекает текущие значения последовательно с каждого элемента управления и формирует из них длинную строку пар ID=значение. Затем эта строка отправляется на сервер странице, указанной в атрибуте action дескриптора <form>, используя метод post, или метод get. Если атрибут action не указан, то данные отправляются той же странице. Сам HTML не устанавливает на количество форм никаких ограничений, а ASP.NET устанавливает и для одной активной страницы способна генерировать только одну HTML-форму <form></form>.

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

    <html>
    <head>
        <title>Посылка сообщений</title>
        <meta content="text/html; charset=windows-1251" http-equiv="Content-Type">
    </head>
    <body>
        <form method="get" name="form1">
            <p>
                Ваше сообщение:
                <input id="message" name="message" type="text" value="Hello, world!">
                <br>
                Кто посылает:
                <input id="autor" name="autor" type="text" value="CHETKOB B.M.">
            </p>
            <p>
                Кто примкнул к сообщению:
            </p>
            <ul>
                <li>
                    <input id="P" name="P" type="checkbox" value="checkbox">
                    Петров Петр </li>
                <li>
                    <input id="I" name="I" type="checkbox" value="checkbox">
                    Иванов Иван </li>
            </ul>
            <p>
                <input id="OK" type="submit" value="Submit">
            </p>
        </form>
    </body>
    </html>

    Нажмите на кнопку Submit и посмотрите в поле адреса броузера. Форма отослала на сервер строку

    WebForms.htm?message=Hello%2C+World%21autor=CHETKOB+B.M.OK=Submit

    Поскольку в форме указан метод ?) оказались присоединены пары имя=значение, разделенные амперсандом ( ). Обратите внимание, что символы пробела, восклицательный знак, запятая (и если вы введете русский текст) заменяются двухбайтными кодами с предваряющим символом процента ( % ).

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

    Мы уже говорили, что если такую страницу разместить на сервере, то не смотря на расширение .aspx, ASP.NET -машина ее все равно обрабатывать не будет. Для этого HTML-дескрипторы нужно сделать активными (серверными) добавлением атрибута runat="server". В связи с этим сделаем два определения: та страница, которая не содержит серверных дескрипторов, называется статической (пассивной). Если же на странице есть хоть один дескриптор с указанным атрибутом, то такая страница подлежит дальнейшей обработке системой ASP.NET (или иной) и называется динамической (активной).

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

    Пример кодирования страницы

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

    При помещении элементов с вкладки HTML на страницу атрибут runat="server" автоматически не добавляется и такой элемент средой на сервере обрабатываться не будет. Следовательно, кодировать на языке программирования его будет нельзя. Прежде элемент нужно пометить как серверный, введя напрямую указанный атрибут в дескрипторное представление элемента, или в оболочке в режиме Design на выделенном элементе вызвать контекстное меню и выполнить опцию Run As Server Control (сделать серверным элементом управления)

    Если мы разрабатываем активную страницу "с нуля", то лучше сразу применять серверные элементы управления, где уже автоматически встроен дескриптор runat="server". И более того, если его убрать, это будет считаться ошибкой. Дескрипторы, представляющие серверные элементы, начинаются с метки asp:Тип_элемента, далее следуют атрибуты элемента.

    Для примера создадим простую страницу.

  • Создайте новый сайт командой File/New/Web Site..., настроив окно мастера примерно так
  • После щелчка на кнопке OK будет создана начальная страница, которая состоит из двух файлов: Default.aspx и Default.aspx.cs.

    Существует два вида кодировки страниц ASP.NET:

  • Страницы со встроенным кодом (совмещенным), когда весь код C# и HTML-дескрипторы хранятся в одном файле .aspx
  • Страницы со скрытым кодом (раздельным, застраничным), когда код C# хранится отдельно от кода с дескрипторами в файле .aspx.cs
  • Второй способ лучше и начальная страница сайта создается именно так. Большой Билл рекомендует использовать именно второй способ, чтобы разделить дизайн страницы и ее функциональность. Но для тренировки один разок мы воспользуемся первым способом.

  • Через панель
  • В редакторе HTML-дескрипторов скорректируйте декларацию Page, чтобы она стала такой
    <%@ Page Language="C#" %>
  • Сразу под декларацией создайте контейнер <script runat="server"></script> для будущего размещения в нем кода C#. Это можно сделать либо напрямую в режиме Source, либо в режиме Design двойным щелчком на странице создать обработчик события по умолчанию Page_Load(), который оболочка поместит в блок скриптов (а затем за ненадобностью обработчик можно удалить)
  • Полность удалите декларацию DOCTYPE (не обязательно, но чтобы не мешала)
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • Теперь мы получили страницу, где код C# нужно будет размещать в контейнеры <script runat="server"></script>. Этих контейнеров на странице может быть сколько угодно и они могут располагаться в любом месте. ASP.NET перед выполнением всеравно скомпилирует страницу, пройдя весь ее код. Но все же рекомендуется иметь один контейнер и располагать его вначале страницы (для удобства бедного сопровождающего программиста). Начальная заготовка страницы будет выглядеть так

    <%@ Page Language="C#" %>
    	
    <script runat="server">
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
    </html>

    Создание обработчиков

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

    Обработчик загрузки страницы

    Напомним, что каждый раз при поступлении на сервер первого запроса или обратной отсылки ( постинга ) с данными инициируется создание объекта страницы. И как только объект страницы будет загружен в память и будут созданы и инициализированы все элементы страницы, срабатывает событие Load, с которым мы свяжем функцию-обработчик с нужным нам кодом.

    Обычно, при разработке визуальных элементов управления, имеющих события, назначают событие по умолчанию для того, чтобы оболочка при двойном щелчке на элементе в режиме Design автоматичеси создавала обработчик именно этого события. Например, для элемента Button это событие Click, для флажка CheckBox - CheckedChanged, и т.д. Событие по умолчанию назначается с помощью атрибута [DefaultEvent("Имя_события")].

  • Переведите страницу в режим Design и двойным щелчком на ней создайте заготовку обработчика события по умолчанию Load, которое срабатывает каждый раз, когда страница на сервере загружается в память. Теперь в режиме Source страница будет выглядет так
    (рис ) Страница после создания обработчика события Load<%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
    
        }  
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
            </div>
        </form>
    </body>
    </html>
    Заметьте, что в дескрипторе <form> нет атрибутов action и method, это значит, что данные от клиента форма будет посылать этой же странице, и посылать их будет методом post (метод get присоединяет данные элементов управления к URL-адресу). Это нам и нужно, поэтому ничего не будем менять.
  • Поместите с вкладки

    Исходный код страницы Default.aspx будет таким

    <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
        
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div>
                <asp:Label ID="Label1" runat="server"></asp:Label>
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>
  • Включение режима абсолютного позиционирования элементов управления

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

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

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

  • Переведите страницу .aspx в режим Design, чтобы появилось меню Layout оболочки
  • Выберите команду меню
  • Обработчик переключения экземпляра CheckBox1 элемента CheckBox

  • Вызовите панель
  • Перейдите в панель
  • Центрирование интерфейса

    Отцентрируем наши элементы на странице. Для этого

  • Перейдите на вкладку Source страницы. Установите курсор внутри контейнера

    Обратите внимание, что в меню оболочки для вкладки Source нет пункта Format, который появится только на вкладке Design.

  • Не снимая выделения текста перейдите на вкладку Design.

    Мы видим, что все элементы контейнера <div></div> остались выделенными и появилось меню Format.

  • Отцентрируйте выделенные элементы страницы командой
  • Легко заметить, что эти действия привели к добавлению в дескриптор <div> атрибута внутреннего стиля

    <div style="text-align: center">
    .....................
    </div>

    Это наглядный пример того, что зная где и что подкрутить, можно подкручивать и напрямую. HTML-дескрипторы такие "одноклеточные", что все их поведение "написано у них на лбу". Но чтобы опуститься до их уровня, нам нужно попотеть при подъеме в гору. Серверные элементы из вкладки Standard намного сложнее, зато несравненно функциональней и полностью поддерживают парадигму объектно-ориентированного программирования. Их настройка большей частью выполняется через код C# и панель Properties.

    Стоит упомянуть про режимы редактирования Design и Source. На сервере наша активная страница все равно будет храниться в виде кода, как он представляется на вкладке Source (... и терпеливо ждать своего посетителя). Режим Design является средством визуального проектирования и предназначен для разработчика (нас с вами), чтобы существенно упростить (ускорить) его работу.

    Заполнение обработчиков кодом

    Заполним обработчики. При загрузке странице по первому запросу или обратной отсылке (постинга) мы хотим, чтобы в объект Label1 помещался снимок таймера сервера. Конечно, это не лучший способ спрашивать время у сервера (ходить за семь верст киселя хлебать), когда то же самое можно спросить у таймера клиента. Но пока мы не использовали JavaScript, нам и это сойдет. Кроме этого, в обработчике копируется значение поля объекта TextBox1 в метку Label2 и добавляется восклицание.

    Второй обработчик устанавливает цвет переднего плана и фона метки Label1. Он срабатывает только тогда, когда при обратной отсылке (постинге) обнаружено изменение состояния объекта CheckBox1. Если состояние CheckBox1 между двумя обратными отсылками не изменялось, то события CheckedChanged не возникает и код обработчика не выполняется.

    Полностью код страницы в режиме Source для нашего примера будет таким

    <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = "Текущее время: "
                + DateTime.Now.ToLongTimeString();
            Label2.Text = TextBox1.Text + "!!!";
        }
    	
        protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (CheckBox1.Checked)
            {
                Label1.ForeColor = System.Drawing.Color.White;
                Label1.BackColor = System.Drawing.Color.Red;
            }
            else
            {
                Label1.ForeColor = System.Drawing.Color.Black;
                Label1.BackColor = System.Drawing.Color.White;
            }
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="Label1" runat="server"></asp:Label>
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" OnCheckedChanged="CheckBox1_CheckedChanged" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>
  • Запустите пример, должен получиться внешне приблизительно такой результат
  • Модель событий ASP.NET

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

  • Клиент первый раз запрашивает страницу. Среда исполнения ASP.NET создает объекты страницы и элементов управления, выполняется код инициализации, страница преобразуется в HTML и возвращается клиенту. Объекты страницы сразу же удаляются из памяти сервера, освобождая место для других запросов.
  • Получив HTML-страницу клиент что-то с ней делает и инициирует обратную отсылку - постинг (например, щелкает на кнопке Submit ). Броузер собирает данные со всех элементов формы и события, которые произошли на клиенте, упаковывает все это в пакеты ("пакует чемоданы") и отправляет целевой странице сервера на обработку. Целевая страница указана в атрибуте action HTML-дескриптора формы. Каждый пакет посылается отдельно и с уведомлением успешной доставки.
  • На сервере ASP.NET получает поступившие пакеты, распаковывает и соединяет их. Затем запускает целевую страницу и предает ей полученные данные.
  • Целевая страница загружается в память, повторно создаются объекты с начальной инициализацией как при первой загрузке.
  • Затем серверная страница переводится в состояние, какое она имела перед последней отправкой клиенту. Информацию о последнем состоянии серверной страницы ей передал клиент. Эту информацию он получил от страницы в скрытых полях вместе с последним ответом и теперь вернул в неизменном виде в своем новом запросе (обратной отсылке). Информация о последнем состоянии серверной страницы перед ответом клиенту называется состоянием вида.
  • После этого ASP.NET приступает к анализу поступивших событий и определяет, что клиент хотел сделать с данными перед обратной отсылкой.
  • И наконец, удовлетворяя требования клиента выполняет действия, предписанные событиями в соответствии с имеющимися обработчиками. Тем самым формируется новое состояние серверной страницы и новый HTML-ответ, который отправляется клиенту вместе с новым состоянием вида в скрытых полях.
  • После сгенерированного HTML-ответа страница немедленно выгружается из памяти сервера.
  • Автоматические обратные отсылки

    В нашем примере только кнопка Submit генерирует обратную отсылку на стороне клиента. А любые изменения в элементе TextBox1 или CheckBox1 не будут обработаны до нажатия этой кнопки (фактической отправки формы на серверную обработку). С одной стороны это хорошо, меньше ресурсов тратится на пересылку, но с другой - пользователь не получает немедленный отклик на свои действия.

    В серверных элементах управления такой случай предусмотрен. Если включить свойство AutoPostBack элемента в состояние True, то после завершенных изменений в состоянии элемента автоматически броузером будет инициирована обратная отсылка. Это происходит за счет того, что элемент управления с таким установленным свойством при ответе наряду с HTML генерирует дополнительный скрытый JavaScript -код, заставляющий броузер выполнять обратную отсылку всякий раз, когда происходит изменение состояния этого элемента пользователем.

    Проверим это на нашем предыдущем примере.

  • Выделите на странице Default.aspx объекты TextBox1 и CheckBox1 и в панели Properties установите для них свойство AutoPostBack в значение True. Вы видите, что в дескрипторах этих элементов сразу появился дополнительный атрибут AutoPostBack="True". Теперь при генерировании ответа клиенту эти элементы будут генерировать и JavaScript-код обратной отсылки.
  • Запустите страницу на выполнение и в появившемся окне броузера через контекстное меню выполните опцию View Source. Среди прочего HTML-кода можно увидеть и JavaScript-код обратной отсылки
    ..........................................................
    <script type="text/javascript">
    <!--
    var theForm = document.forms['form1'];
    if (!theForm) {
        theForm = document.form1;
    }
    function __doPostBack(eventTarget, eventArgument) {
        if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
            theForm.__EVENTTARGET.value = eventTarget;
            theForm.__EVENTARGUMENT.value = eventArgument;
            theForm.submit();
        }
    }
    // -->
    </script>
    ..........................................................
        <input id="CheckBox1" type="checkbox" name="CheckBox1" 
        onclick="javascript:setTimeout('__doPostBack(\'CheckBox1\',\'\')', 0)" />
    	
        <input id="TextBox1" type="text" name="TextBox1" value="Сидоров" 
        onchange="javascript:setTimeout('__doPostBack(\'TextBox1\',\'\')', 0)"
        onkeypress="if (WebForm_TextBoxKeyHandler(event) == false) return false;" />
    ..........................................................
    Ах!, какие умные эти серверные элементы: сами сеют - сами жнут - сами песенки поют!!!
  • Поизменяйте состояние элементов в броузере. Теперь на каждые изменения серверу генерируется обратная отсылка (постинг) для обработки. Зачем ждать, как в армии, общей команды Submit...
  • Обратите внимание, что HTML-элементы во вкладке HTML панели Toolbox - слабосильные и автоматическую обратную отсылку не выполняют.

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

    Сохранение состояния вида

    Давайте опять вернемся к нашему простому примеру и выполним следующее:

  • Верните свойство AutoPostBack элементов CheckBox1 и TextBox1 опять в состояние False
  • Выделите элемент Label1 и установите его свойство EnableViewState в состояние False (по умолчанию оно установлено в значение True ). Немедленно в дескрипторе элемента появится атрибут EnableViewState="False"
  • Запустите приложение и отметьте, что цвет элемента Label1 (Текущее время:...) меняется только при первой отсылке после изменения элемента CheckBox1. Последующие отсылки восстанавливают начальный цвет.
  • Что здесь происходит, почему цвет метки выполняет только первое новое указание элемента CheckBox1... Все дело в протоколе HTTP и во времени жизни страницы. HTTP -протокол не хочет сохранять последнее состояние страницы, считая очередной запрос к странице первым, а сама страница существует только на время обработки запроса и затем немедленно выгружается, чтобы освободить ресурсы сервера для работы других страниц.

    Наша метка Label1 обновляет цвет только тогда, когда срабатывает обработчик изменения состояния элемента CheckBox1. А это происходит только тогда, когда броузер пришлет информацию, что произошло событие изменения состояния элемента CheckBox1.

    Допустим, что пользователь поставил галочку в CheckBox1. Броузер прислал это событие на страницу. ASP.NET это событие распознала, запустила наш обработчик изменения цвета и в соответствии с новым состоянием CheckBox1 (checked) выполнила код

    Label1.ForeColor = System.Drawing.Color.White;
    Label1.BackColor = System.Drawing.Color.Red;

    Этим кодом объект Label1 будет настроен и сгенерирует броузеру соответствующий HTML-код цветной метки. Но при следующей обратной отсылке, если оставить объект CheckBox1 в том же неизменном состоянии checked, сведений о событии изменения его состояния сервер не получит. Поэтому и делать с элементом Label1 при его создании сервер ничего не будет, а сгенерирует его начальное состояние, установленное в панели Properties. Что пользователь в результате получит от сервера - начальные установки цвета Label1. У него стоит галочка цветности метки, а она присылается совсем с другим, начальным, цветом.

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

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

    <input type="hidden" 
    	       name="__VIEWSTATE" id="__VIEWSTATE" 
    	       value="/wEPDwULLTE2ODAxODI5OTYPZBYCAgMPZBYEAgMPEA
    	              8WAh4HQ2hlY2tlZGdkZGRkAgcPDxYCHgRUZXh0BRHQodC90LXRgtC6
    	              0L7QsiEhIWRkGAEFHl9fQ29udHJvbHNSZXF1aXJlUG9zdEJhY2tLZX
    	              lfXxYBBQlDaGVja0JveDEKD82+p/Oho3OWiA2YZad613t4ZA==" />

    Атрибут value скрытого поля hidden имеет значение упакованного состояния элементов управления, которое вместе с ответом передается на временное хранение броузеру.

  • Восстановите механизм сохранения состояния вида элементу Label1 нашей страницы, вернув свойство EnableViewState в значение True. Проверьте работу элемента Label1, запустив страницу. Теперь все работает как надо - цветность метки сохраняется при каждом ответе сервера!
  • Отметьте для себя, что серверные элементы сохраняют интерфейсные значения даже при выключенном механизме сохранения состояния вида. Про несерверные (нет атрибута runat="server" ) HTML-элементы такого не скажешь. Web-элементы с вкладки Standard - все серверные.

    Для примера добавьте на страницу (в любое место) из вкладки HTML элемент Input (Checkbox). Выполните страницу, измените состояние элемента и отправьте страницу на сервер. Сервер вернет отклик с первоначальным состоянием элемента. Если же превратить элемент Input (Checkbox) в серверный командой Run As Server Control, то элемент при постинге будет сохранять свое состояние.

    Этапы выполнения страниц на сервере

    Со стороны сервера обработка Web-формы (страницы) средой ASP.NET происходит поэтапно. На каждом этапе генерируются соответствующие события, которые наш код может перехватывать и выдавать нужный ответ. Поток обработки страницы проходит следующие этапы:

  • Инициализация структуры страницы
  • Инициализация кода пользователя
  • Проверка достоверности данных
  • Обработка событий
  • Автоматическая привязка данных
  • Очистка
  • Графически это можно изобразить так

    Инициализация структуры страницы

    На этом этапе страница загружается в память, расшифровывается информация о состоянии вида (если это обратная отсылка), запускается событие Page.Init. Но это событие редко обрабатывается разработчиком, поскольку объекты элементов управления еще не созданы, а информация о состоянии вида еще полностью не расшифрована.

    Инициализация кода пользователя

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

    Для определения состояния страницы можно использовать свойство Page.IsPostBack, которое будет иметь значение false при первом запросе страницы. IsPostBack является статическим свойством класса Page. Оно всегда возвращает информацию, основанную на текущей странице. Можно также использовать свойство экземпляра как this.IsPostBack, что равносильно Page.IsPostBack.

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

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

    В тех случаях, когда действительно легче инициализировать элемент управления в коде, лучше отключить состояние вида для этого элемента значением EnableViewState="false" и инициализировать элемент каждый раз при запуске события Page.Load независимо от того, является ли текущий запрос обратной отсылкой или поступил впервые. В качестве упражнения выполним программное восстановление состояния метки в зависимости от состояния элемента управления CheckBox1 нашего примера

  • Выделите элемент управления Label1 и через панель Properties установите для него EnableViewState="false"
  • Включите флажок CheckBox1 и многократно выполните обратную отсылку, чтобы убедиться, что состояние вида Label1 не сохраняется
  • Дополните блок скриптов страницы Default.aspx кодом явного вызова обработчика CheckBox1_CheckedChanged()
  • <%@ Page Language="C#" %>
    	
    <script runat="server">
        protected void Page_Load(object sender, EventArgs e)
        {
            Label1.Text = "Текущее время: "
                + DateTime.Now.ToLongTimeString();
            Label2.Text = TextBox1.Text + "!!!";
                
            // Восстанавливаем Label1 программно место состояния вида   
            CheckBox1_CheckedChanged(null, EventArgs.Empty); 
        }
    	
        protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
        {
            if (CheckBox1.Checked)
            {
                Label1.ForeColor = System.Drawing.Color.White;
                Label1.BackColor = System.Drawing.Color.Red;
            }
            else
            {
                Label1.ForeColor = System.Drawing.Color.Black;
                Label1.BackColor = System.Drawing.Color.White;
            }
        }
    </script>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Начальная страница</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="Label1" runat="server" EnableViewState="false" />
                nbsp;nbsp;nbsp;
                <asp:CheckBox ID="CheckBox1" runat="server"
                    Text="Red" OnCheckedChanged="CheckBox1_CheckedChanged" /><br />
                Имя:
                <asp:TextBox ID="TextBox1" runat="server">Снетков</asp:TextBox><br />
                Привет дорогой товарищ (господин)
                <asp:Label ID="Label2" runat="server"></asp:Label><br />
                <br />
                <asp:Button ID="Button1" runat="server" Text="Обновить" />
            </div>
        </form>
    </body>
    </html>

    Теперь мы видим, что элемент Label1 работает правильно, восстанавливая свое состояние программно в зависимости от состояния флажка.

    Проверка достоверности

    При формировании обратной отсылки броузер собирает с элементов дескриптора <form> значения полей и состояния. Он не проверяет соответствие типов собираемых данных ожидаемым значениям. Например, если текстовое поле ожидает цифры, а пользователь ввел туда буквы, значение поля нормально будет передано на сервер. Но серверный код ожидает цифры и готов применить к ним математические операции.

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

  • Запустите страницу Default.aspx и введите в поле TextBox1 какой-нибудь текст в угловых скобках
  • Сервер немедленно прервет выполнение страницы и выдаст сообщение Server Error in '/WebSite1' Application с подробным анализом ошибки.

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

    Но и серверная сторона также проверяет достоверность поступивших данных, прежде чем начать их обработку. В ASP.NET предусмотрены специальные классы ( валидаторы ), которые выполняют проверку достоверности для закрепленных за ними элементов управления. Но существует общий способ проверить достоверность всей страницы сразу. Достаточно в каком-нибудь обработчике проверить значение свойства страницы Page.IsValid.

    Обработка событий

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

  • События, требующие немедленного ответа сервера (щелчок на кнопке Submit, изменение состояния элемента управления с установленным свойством AutoPostBack, и др.). Эти события инициируют немедленную обратную отсылку.
  • Отложенные события изменения состояния - включают смену выбора в элементе управления или изменение текста в текстовом поле, но обнаруживаемые ASP.NET только по прибытию очередной обратной отсылки.
  • Такая модель событий ASP.NET отличается от традиционных локальных Windows-приложений. В Windows-приложении состояние формы (окна) целиком находится в памяти и способно немедленно отреагировать на происшедшее событие. В Web-приложении обработка событий формы (страницы) происходит только при поступлении обратной отсылки на сервер. В связи с этим на сервер поступает сразу несколько необработанных событий и важно знать, в какой последовательности они обрабатываются.

    Например, пусть у нас есть текстовое поле без автоматической обратной отсылки и кнопка Submit. Мы изменяем текстовое поле и щелкаем на кнопке Submit, требуя от броузера обратную отсылку. Запрос поступает на сервер, анализируется средой исполнения ASP.NET, которая запускает следующие события в таком порядке:

  • Page.Init
  • Page.Load
  • TextBox.TextChanged
  • Button.Click
  • Page.PreRender
  • Page.Unload
  • Запоминание этой информации может быть решающим в облегчении нашей жизни как программистов ASP.NET. Нельзя подсовывать сырые заготовки на выходе конвейера готовой продукции. Это также важно, как помнить о том, что страница существует только на время формирования очередного ответа клиенту и никакие переменные приложения не сохраняют своих значений до следующего поступления обратной отсылки. В этом состоит отличительная особенность Web-приложений.

    Автоматическая привязка данных

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

    Существуют два типа операций с источниками данных. Любые изменения (вставка, удаление, изменение записей БД) выполняются после обработки всех событий элементов управления, но непосредственно перед генерацией события Page.PreRender. После запуска события Page.PreRender наступает черед элементов управления источниками данных, которые выполняют свои запросы и вставляют извлеченные данные в связанные с этими данными элементы управления. При этом ни один обработчик событий элементов отображения уже не будет иметь доступа к новым данным.

    Это последняя остановка в жизненном цикле страницы. Исторически сложилось так, что событие Page.PreRender обозначает последнее действие перед преобразованием страницы в HTML.

    Очистка

    В конце своего жизненного цикла страница преобразуется в HTML. После этого начинается реальная очистка и запускается событие Page.Unload. В этот момент объекты страницы все еще доступны, но окончательный HTML уже сгенерирован и не может быть изменен. Затем система теряет ссылку на объект страницы и сборщик мусора уничтожает брошенную память. Последним предсмертным писком страницы становится событие Page.Disposed, когда сборщик мусора доберется до нее. С получением сервером очередного запроса страницы все начинается заново.

    Пример страничного потока событий

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

    Добавление к сайту новой страницы

  • Выполните опцию меню оболочки Website/Add New Item... или такую же опцию в контекстном меню узла сайта панели Solution Explorer
  • Заполните окно мастера добавления как показано на снимке. Имя странице присвойте
  • Выполните команду Website/Set As Start Page (сделать страницу стартовой), чтобы при запуске проекта из оболочки она выполнялась первой. Такая установка применяется только в среде разработки и не влияет на порядок загрузки страниц на промышленном сервере.
  • Поместите на страницу из вкладки Standard панели Toolbox серверный элемент управления Label и Button. Отцентрируйте их в режиме Design командой Format/Justify/Center
  • Переименуйте элемент Label1 в lblInfo
  • Через панель Document Outline в режиме Design выделите объект lblInfo и в панели Properties установите свойство EnableViewState в состояние false
  • Отметьте для себя, что установленные отличными по умолчанию значения свойств элементов управления выделяются в поле значений панели Properties полужирным стилем шрифта.

  • Очистите значение свойства Text элемента lblInfo
  • Двойным щелчком на странице в режиме Design создайте заготовку обработчика Page_Load() и растиражируйте ее редактором кода еще на три обработчика
  • Двойным щелчком на кнопке Button1 в режиме Design создайте для нее заготовку обработчика Button1_Click()
  • Заполните обработчики следующим кодом
    (рис ) Файл отделенного кода PageFlow.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
        
    public partial class PageFlow : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Объекты созданы и инициализированы начальными значениями
            // и возвращенным состоянием вида
            lblInfo.Text += "Сработал обработчик события Load <br />";
    	
            if (Page.IsPostBack)
            {
                lblInfo.Text += "<b>Эта страница запрашивается не впервые</b><br />";
            }
        }
    	
        protected void Page_Init(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события Init <br />";       
        }
    	
        protected void Page_PreRender(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события PreRender <br /><hr />";       
        }
    	
        protected void Page_Unload(object sender, EventArgs e)
        {
            // Не увидим, HTML уже сгенерирован
            lblInfo.Text += "Сработал обработчик события Unload <br />";       
        }
    	
        protected void Button1_Click(object sender, EventArgs e)
        {
            lblInfo.Text += "Сработал обработчик события Click кнопки <br />";       
        }
    }
    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="PageFlow.aspx.cs" Inherits="PageFlow" %>
    	
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
            <div style="text-align: center">
                <asp:Label ID="lblInfo" runat="server" EnableViewState="False"></asp:Label>
                <br />
                <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
            </div>
        </form>
    </body>
    </html>
  • Запустите приложение первый раз. Сервер сгенерирует следующий HTML-ответ
  • Щелкните на кнопке
  • Обратите внимание на файл поддержки PageFlow.aspx.cs. Он не полон - в производном классе страницы нет объявлений объектов lblInfo и Button1 элементов управления. Такое в языке C# недопустимо. Однако страница нормально компилируется и успешно выполняется. Дело все в ключевом слове partial (частичный) в объявлении класса. Это нововведение в Visual Studio 2005. Оболочка сама перед компиляцией файла добавляет необходимые объявления объектов на основании информации о дескрипторах, связанных с серверными элементами на основной странице .aspx. Это справедливо, поскольку на странице со смешанным кодом и раньше не было никаких объявлений (как и не было классов).

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

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

    Страница как контейнер элементов управления

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

    Каждый раз, когда страница создается в ответ на HTTP-запрос, система ASP.NET инспектирует файл .aspx. Для каждого найденного дескриптора элемента управления она создает и настраивает объект элемента управления, а затем добавляет этот объект к странице как ее дочерний элемент управления. Ссылки на все дочерние элементы страницы размещаются в коллекции Page.Controls.

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

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

  • Добавьте к проекту новую страницу с именем ControlTree.aspx с раздельным кодом в файле ControlTree.aspx.cs.
  • Пометьте ее как стартовую
  • Поместите на страницу элемент управления Panel из вкладки Standard панели Toolbox
  • Поместите на панель три кнопки Button, одну метку Label и текстовое поле TextBox
  • Разместите за пределами панели, но внутри дескриптора <form>, внизу еще одну кнопку Button
  • Введите сверху и снизу за пределами дескриптора <form> статический HTML-текст внутри дескрипторов параграфа <p></p>
  • Код разметки для полученной страницы должен выглядеть примерно так

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="ControlTree.aspx.cs" 
        Inherits="ControlTree" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title>Дерево элементов управления</title>
    </head>
    <body>
        <p>
            <i>Это статический текст 1</i></p>
        <form id="Controls" method="post" runat="server">
            <div>
                <asp:Panel ID="MainPanel" runat="server" Height="121px" Width="267px">
                    <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 />
                    <asp:Label ID="Label1" runat="server" Text="Label">Name:</asp:Label>
                    <asp:TextBox ID="TextBox1" runat="server" Width="195px"></asp:TextBox>
                </asp:Panel>
                <asp:Button ID="Button4" runat="server" Text="Button4" />
            </div>
        </form>
        <p>
            <i>Это статический текст 2</i></p>
    </body>
    </html>

    Представление этого кода разметки в окне броузера имеет такой вид

  • Установите для кнопки Button4 свойство ToolTip (всплывающая подсказка) в состояние Сканировать все
  • Создайте обработчик кнопки Button4
  • Заполните кодовый файл так
    (рис ) Код файла ControlTree.aspx.csusing System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
        
    public partial class ControlTree : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Начало сканирования всех нелитеральных элементов управления
            DisplayControl(Page.Controls, 0, false);
    	
            // Горизонтальная линия по окончании вывода
            Response.Write("<hr />");
        }
    	
        protected void Button4_Click(object sender, EventArgs e)
        {
            // Начало сканирования всех литеральных элементов управления
            DisplayControl(Page.Controls, 0, true);
    	
            // Горизонтальная линия по окончании вывода
            Response.Write("<hr />");
        }
    	
        private void DisplayControl(ControlCollection controls, int depth, bool literalFlag)
        {
            foreach (Control control in controls)
            {
                // Выявлять литеральные (статические) элементы управления
                bool literalControl = control is LiteralControl;
    	
                // Отобразить элемент управления
                if (literalControl == literalFlag)
                {
                    // Использовать параметр depth для определения
                    // отступов в выводе дерева элементов управления
                    Response.Write(new String('-', depth * 4) + "-->");
    	
                    String literalText = "";
                    if (literalControl)
                        literalText = "text" + ((LiteralControl)control).Text;
    	
                    Response.Write(control.GetType().ToString() + " - <b>"
                        + control.ID + literalText + "</b><br />");
                }
    	
                // Проверять дочерние элементы управления рекурсивно
                if (control.Controls != null)
                {
                    DisplayControl(control.Controls, depth + 1, literalFlag);
                }
            }
        }
    }
  • Запустите страницу. После щелчка на кнопке Button4 должен получиться примерно такой HTML-выход
    -->System.Web.UI.HtmlControls.HtmlHead - 
    ------>System.Web.UI.HtmlControls.HtmlTitle - 
    -->System.Web.UI.HtmlControls.HtmlForm - Controls
    ------>System.Web.UI.WebControls.Panel - MainPanel
    ---------->System.Web.UI.WebControls.Button - Button1
    ---------->System.Web.UI.WebControls.Button - Button2
    ---------->System.Web.UI.WebControls.Button - Button3
    ---------->System.Web.UI.WebControls.Label - Label1
    ---------->System.Web.UI.WebControls.TextBox - TextBox1
    ------>System.Web.UI.WebControls.Button - Button4
    -->System.Web.UI.LiteralControl - text 
    -->System.Web.UI.LiteralControl - text 
    
    Это статический текст 1
    
    
    ------>System.Web.UI.LiteralControl - text
    
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    
    ---------->System.Web.UI.LiteralControl - text 
    ---------->System.Web.UI.LiteralControl - text 
    ------>System.Web.UI.LiteralControl - text 
    ------>System.Web.UI.LiteralControl - text 
    -->System.Web.UI.LiteralControl - text 
    
    Это статический текст 2
  • Объекты LiteralControl, которые мы определяли в нашем коде и отдельно распечатывали по кнопке Button4, предоставляют немногое в отношении функциональности. Например, мы не можем программно устанавливать информацию, связанную со стилями, наподобие цвета или шрифта. У них также нет уникальных серверных идентификаторов. Однако мы можем манипулировать содержимым LiteralControl с использованием его свойства Text, что и продемонстрировано в последнем примере.

    Здесь при рекурсивном обходе дерева элементов управления мы учитывали тот факт, что ASP.NET генерирует страницу иерархически. Она генерирует непосредственно только верхний уровень элементов управления, т.е. только свойство-контейнер ( коллекцию элементов управления ) Controls самой страницы. Если какой-то элемент управления содержит дочерние элементы, т.е. сам является контейнером, то он имеет свое собственное свойство-контейнер Controls.

    Обратите внимание, что задав свойство ToolTip конопки Button4, мы организовали для пользователя появление всплывающей подсказки при наведении курсора мыши на кнопку. Фактически серверный элемент Button4 сгенерировал в своем HTML-выводе атрибут title="Сканировать все".

    Программное управление заголовком страницы

    Ранее мы говорили, что чтобы сделать дескриптор HTML элементом управления, необходимо добавить в него атрибут runat="server". Но в сканированном выводе последнего примера система ASP.NET нашла элемент управления заголовком страницы

    System.Web.UI.HtmlControls.HtmlHead

    Visual Studio 2005 автоматически считает дескриптор <head> серверным элементом. Значит мы можем управлять им программно. Объект HtmlHead наследует предопределенный интерфейс IPageHeader и содержит следующие свойства:

  • Title - название HTML-страницы, которое отображается в заголовке броузера
  • LinkedStyleSheets - коллекция объектов для настройки стилей всего документа через внешнюю таблицу стилей
  • StyleSheet - настройка встраиваемых стилей
  • Metadata - коллекция объектов для настройки дескрипторов метаданных
  • Пример:

    Page.Header.Title = "Это программно заданный заголовок";
    Page.Header.Metadata.Add("Keywords", "Рыжик, Пыжик, ГдеТыБыл");
    Page.Header.Metadate.Add(Description", "Русские народные мелодии");

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

    Пример создания элемента управления во время выполнения

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

  • Добавьте к проекту предыдущего примера новую страницу с разделенным кодом и именем DynamicButton. Сделайте страницу стартовой.
  • Поместите на страницу из вкладки Standard элемент управления Panel, метку Label, три кнопки Button, а из вкладки HTML - элемент Horizontal Rule
  • Настройте интерфейс страницы времени проектирования так

  • Заполните обработчик загрузки страницы следующим кодом
    (рис ) Начальный код динамического создания кнопкиpublic partial class DynamicButton : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать новый объект кнопки
            Button newButton = new Button();
    	
            // Присвоить значения свойств
            newButton.ID = "newButton";// Пометили
            newButton.Text = "* Dynamic Button *";
    	
            // Добавить кнопку в коллекцию элементов управления формы
            form1.Controls.Add(newButton);
        }
    }
  • Запустите страницу на выполнение. Получится такой результат
  • Мы получили то, что динамически созданная кнопка оказалась в конце декскриптора <form>. Для получения большего контроля над расположением динамического элемента управления можно использовать элемент управления PlaceHolder. Целью элемента управления PlaceHolder (держатель места) является размещение других элементов управления. Если разместить элемент PlaceHolder на форме и не добавить в его коллекцию Controls других элементов, он ничего не изменит на окончательном HTML-выводе.

  • Поместите элемент управления PlaceHolder впереди кнопки с названием "Reset Text" и перепишите код так, чтобы динамически создаваемая кнопка добавлялась не в коллекцию формы, а в коллекцию PlaceHolder
    // Добавить кнопку в коллекцию PlaceHolder1
    PlaceHolder1.Controls.Add(newButton);
  • Теперь HTML-вывод должен быть таким

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

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

    Если мы собираемся взаимодействовать с динамически созданным элементом управления дальше, то ему следует присвоить уникальный идентификатор, что мы и сделали в нашем примере. Мы можем использовать этот идентификатор для извлечения элемента управления из коллекции Controls родительского контейнера. Можно найти элемент управления с использованием рекурсивной логики поиска, как мы это делали в странице ControlTree.aspx. Можно для поиска элемента использовать статический метод Page.FindControl().

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

  • Двойным щелчком на кнопке с названием Remove Button создайте для нее обработчик, который заполните так
    (рис ) Обработчик события кнопки Remove Button в файле DynamicButton.aspx.csprotected void Button3_Click(object sender, EventArgs e)
    {
        // Поиск кнопки вне зависимости от иерархического
        // уровня, в котором она расположена
        Button foundButton = (Button)Page.FindControl("newButton");
    	
        // Удаление кнопки с проверкой условия, чтобы
        // не пытались удалить уже удаленный объект
        if (foundButton != null)
        {
            // Удаляем из коллекции родительского элемента управления
            foundButton.Parent.Controls.Remove(foundButton);
        }
    }
  • Следует заметить, что последний оператор приведенного кода Remove() не удаляет объект кнопки физически, а удаляет только ссылку на кнопку из коллекции PlaceHolder. Это значит, что кнопка, однажды созданная в обработчике Page_Load(), продолжает существовать в контейнере Page страницы и мы всегда можем ее использовать дальше.

    Динамически добавленные элементы управления могут обрабатывать события. Нужно лишь подключить обработчик события с использованием кода-делегата. Подключать события обязательно нужно в обработчике Page_Load(), иначе будет поздно, поскольку все поступившие с обратной отсылкой события клиента генерируются сразу после события Page.Load. Если код подключения события с помощью делегата разместить в другом месте, обработчик события не сработает, поскольку система это событие уже сгенерирует и, не найдя соответствующий обработчик, оставит без внимания.

  • Добавьте в конец обработчика Page_Load() следующий код
    (рис ) Подключение обработчика события динамически созданного элемента управленияprotected void Page_Load(object sender, EventArgs e)
    {
        // Создать новый объект кнопки
        Button newButton = new Button();
    	
        // Присвоить значения свойств
        newButton.ID = "newButton";
        newButton.Text = "* Dynamic Button *";
    	
        // Добавить кнопку в коллекцию PlaceHolder1
        PlaceHolder1.Controls.Add(newButton);
    	
        // Подключение обработчика события Button.Click
        // динамически созданного элемента управления
        newButton.Click += new EventHandler(newButton_Click);
    }
    	
    void newButton_Click(object sender, EventArgs e)
    {
        Label1.Text = "Этот текст прислал обработчик" + 
                      " динамически созданной кнопки";
    }
  • Сбросьте для метки Label1 свойство сохранения состояния вида, назначив EnableViewState=false
  • Окончательный вариант страницы должен быть таким

    <%@ Page Language="C#" AutoEventWireup="true" 
        CodeFile="DynamicButton.aspx.cs" Inherits="DynamicButton" %>
    	
    <html xmlns="http://www.w3.org/1999/xhtml" >
    <head runat="server">
        <title>Untitled Page</title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
            <asp:Label ID="Label1" runat="server" Text="Щелкните на динамически созданной кнопке"
                Width="431px" EnableViewState="False"></asp:Label><br />
            <br />
            <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            nbsp;
            <asp:Button ID="Button1" runat="server" Text="Reset Text" /><br />
            <hr />
            <asp:Button ID="Button2" runat="server" Text="Create Button" />
            nbsp;nbsp;
            <asp:Button ID="Button3" runat="server" Text="Remove Button" OnClick="Button3_Click" />
        </div>
        </form>
    </body>
    </html>
    using System;
    using System.Data;
    using System.Configuration;
    using System.Collections;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    	
    public partial class DynamicButton : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            // Создать новый объект кнопки
            Button newButton = new Button();
    	
            // Присвоить значения свойств
            newButton.ID = "newButton";
            newButton.Text = "* Dynamic Button *";
    	
            // Добавить кнопку в коллекцию PlaceHolder1
            PlaceHolder1.Controls.Add(newButton);
    	
            // Подключение обработчика события Button.Click
            // динамически созданного элемента управления
            newButton.Click += new EventHandler(newButton_Click);
        }
    	
        void newButton_Click(object sender, EventArgs e)
        {
            Label1.Text = "Этот текст прислал обработчик" + 
                          " динамически созданной кнопки";
        }
    	
        protected void Button3_Click(object sender, EventArgs e)
        {
            // Поиск кнопки вне зависимости от иерархического
            // уровня, в котором она расположена
            Button foundButton = (Button)Page.FindControl("newButton");
    	
            // Удаление кнопки с проверкой условия, чтобы
            // не пытались удалить уже удаленный объект
            if (foundButton != null)
            {
                // Удаляем из коллекции родительского элемента управления
                foundButton.Parent.Controls.Remove(foundButton);
            }
        }
    }
  • Запустите и испытайте закодированную страницу DynamicButton.aspx
  • В окне броузера мы видим, что при каждом вызове страницы динамическая кнопка создается на сервере и посылается клиенту. При нажатии кнопки Remove Button посланное на сервер событие вызывает срабатывание обработчика удаления кнопки из коллекции. Посылка данных на сервер при нажатии других незапрограммированных кнопок опять создает динамическую кнопку и добавляет ее в контейнер. Щелчок на динамической кнопке запускает обработчик смены текста в метке Label1. Щелчок на любой другой кнопке восстанавливает первоначальное содержимое метки Label1, поскольку мы сняли свойство сохранения состояния вида.

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

    Класс Page

    Класс System.Web.UI.Page является базовым для классов создаваемых страниц. В локальных приложениях такую роль играет класс Form. Это значит, что динамические страницы наследуют множество полезных свойств и методов класса Page. Класс Page служит контейнером имен всех элементов управления, входящих в состав страницы. ASP.NET создает новое виртуальное пространство имен, гарантирующее всем дочерним элементам управления уникальность имен в пределах всего дерева элементов управления.

    Свойства класса Page можно разделить на три группы:

  • Внутренние объекты
  • Рабочие свойства
  • Специфические страничные свойства
  • На данном этапе мы коснемся только первой группы свойств.

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

    Внутренние объекты ASP.NET в составе класса Page
    Свойство-объект Порождающий класс Описание
    Application System.Web.HttpApplicationState Содержит информацию о состоянии приложения
    Cache System.Web.Caching.Cache Следит за приоритетностью и устареванием элементов
    Request System.Web.HttpRequest Содержит текущий запрос HTTP
    Response System.Web.HttpResponse Осуществляет отправку ответа клиенту
    Server System.Web.HttpServerUtility Предоставляет вспомогательные методы для отправки Web-запросов
    Session System.Web.SessionState.HttpSessionState Управляет данными, связанными с определенным пользователем
    Trace System.Web.TraceContext Осуществляет трассировку выполнения страницы
    User System.Security.Principal.IPrincipal Представляет пользователя, от которого поступил запрос

    Объекты Session, Application, Cache

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

    Объект Application содержит словарь данных имя=значение, глобальных для всего приложения.

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

    Объект Request

    Этот объект представляет значения и свойства HTTP -запроса, вызвавшего загрузку страницы. Он содержит все параметры URL и другую информацию, отправляемую клиентом. Мы можем использовать объект Request, чтобы обнаружить, какой броузер используется клиентом, или устанавливать и анализировать cookie -наборы ( куки ). Ниже приведены наиболее распространенные свойства класса System.Web.HttpRequest, порождающего объект Request

    Свойства класса System.Web.HttpRequest
    Свойство Порождающий класс Описание
    ApplicationPath string Виртуальный каталог приложения ASP.NET
    PhysicalPath string Реальный каталог приложения ASP.NET
    AnonymousID string Однозначно идентифицирует текущего пользователя при включенном анонимном доступе
    Browser System.Web.HttpBrowserCapabilities Содержит свойства, которые описывают различные возможности броузера пользователя. Например, поддержку элементов управления ActiveX, cookie -наборов, активных сценариев, фреймов и т.д.
    ClientCertificate System.Web.HttpClientCertificate Предоставляет сертификат безопасности для текущего запроса, если таковой существует
    Cookies System.Web.HttpCookieCollection Предоставляет коллекцию cookie -наборов, прибывших с текущим запросом (постингом)
    FilePath string Возвращает реальный путь (относительно сервера) к файлу страницы, запустившей процесс выполнения
    CurrentExecutionFilePath string Содержит реальный путь новой страницы, если мы программно переместили пользователя на новую страницу с помощью метода Server.Transfer() без его уведомления (без полного цикла). Иначе содержит то же, что и FilePath
    Form System.Collections.Specialized.NameValueCollection Представляет коллекцию переменных формы, поступивших с обратной отсылкой (постингом). В большинстве случаев мы будем извлекать эту информацию из свойств элемента управления вместо использования этого набора
    Headers System.Collections.Specialized.NameValueCollection Предоставляет коллекцию HTTP-заголовков имя=значение
    ServerVariables System.Collections.Specialized.NameValueCollection Предоставляет коллекцию глобальных серверных переменных имя=значение
    IsAuthenticated bool Возвращает true при успешной аутентификации пользователя
    IsSecureConnection bool Возвращает true при успешном подключении пользователя по протоколу защищенных сокетов (Secure Sockets Layer - SSL)
    IsLocal bool Возвращает true при запросе пользователем страницы с текущего компьютера
    QueryString System.Collections.Specialized.NameValueCollection Предоставляет параметры, переданные клиентом в строке запроса методом get
    Url System.Uri Содержит URL-адрес текущей страницы
    UrlReferrer System.Uri Содержит URL-адрес страницы, с которой пользователь попал на текущую
    UserAgent string Строка, представляющая тип броузера. Для этого свойства в Microsoft Internet Explorer предусмотрено значение MSIE
    UserHostAddress string Предоставляет IP-адрес клиента
    UserHostName string Предоставляет доменное имя клиента (DNS - Domain Name System - служба имен доменов)
    UserLanguages string Предоставляет отсортированный массив, который перечисляет языковые предпочтения клиента. Может пригодиться при создании многоязычных страниц

    Объект Response

    Этот объект содержит информацию и методы формирования ответа Web-сервера на запрос клиента. В таблице перечислены основные свойства и методы объекта Response.

    Основные свойства и методы объекта Response
    Член Тип Описание
    BufferOutput bool При установке в true (по умолчанию) страница не отправляется клиенту до тех пор, пока не будет полностью сгенерирована (в отличие от отправки по частям при false )
    Cache System.Web.HttpCachePolicy Позволяет конфигурировать кэширование вывода
    Cookies System.Web.HttpCookieCollection Содержит коллекцию cookie-наборов, передаваемых вместе с ответом. Можно использовать для добавления дополнительных cookie -наборов
    Expires int Это свойство можно использовать для кэширования сгенерированного HTML для страницы, что улучшает производительность последующих запросов
    ExpiresAbsolute System.DateTime Это свойство можно использовать для кэширования сгенерированного HTML для страницы, что улучшает производительность последующих запросов
    IsClientConnected bool Указывает на то, подключен ли клиент к серверу. Если нет, можно потребовать остановить длинную операцию
    Write(char);

    Write(char[ ], int, int);

    Write(object);

    Write(string)

    Эти методы позволяют записать текст содержимого соответствующего типа непосредственно в поток ответа. Можно даже записать содержимое файла. Эти методы не так важны и не должны использоваться в сочетании с серверными элементами управления, которые сами заботятся о выводе
    BinaryWrite(byte[ ]) Эти методы позволяют записать текст содержимого соответствующего типа непосредственно в поток ответа. Можно даже записать содержимое файла. Эти методы не так важны и не должны использоваться в сочетании с серверными элементами управления, которые сами заботятся о выводе
    WriteFile(IntPtr, long, long);

    WriteFile(string);

    WriteFile(string, bool);

    WriteFile(string, long, long)

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

    Redirect(string, bool)

    Этот метод направляет пользователя на другую страницу приложения или на другой сайт. Этот метод требует полного цикла с уведомлением пользователя. По сути он отправляет сообщение броузеру, которое заставляет его запросить новую страницу. Метод Server.Transfer() сразу загружает новую страницу и начинает ее обработку. В результате URL, отображаемый в броузере клиента, не меняется. Но этим методом перемещаться на другой сайт нельзя

    Пример:

    // Перенаправляем на файл в текущем каталоге
    Response.Redirect("newpage.aspx");
    .............................
    // Перенаправляем на другой Web-сайт
    Response.Redirect("http://www.dialog-service.net");

    Объект Server

    Основные свойства и методы объекта Server
    Член Тип Описание
    MachineName string Представляет имя компьютера, на котором запускается страница. Это имя Web-сервера, используемое компьютером с целью его идентификации для остальной части сети
    CreateObject(string);

    CreateObject(System.Type)

    Создает экземпляр COM-объекта, определяемый его программным идентификатором progID. Используется для обратной совместимости, поскольку упрощает взаимодействие с COM-объектами в .NET
    GetLastError() Извлекает объект самого последнего исключения или нулевую ссылку, если исключения не было. Используется в обработчике событий приложения, проверяющего сбойные ситуации.
    HtmlEncode(string);

    HtmlEncode(string, System.IO.TextWriter)

    Осуществляет HTML-кодирование строки допустимыми символами, которые не будут восприниматься как управляющие ( < lt; > gt; amp; жесткий пробел nbsp )
    HtmlDecode(string);

    HtmlDecode(string, System.IO.TextWriter)

    Осуществляет обратную операцию по отношению к HTML-кодированной строке символов
    UrlEncode(string);

    UrlEncode(string, System.IO.TextWriter)

    Заменяет обычную строку строкой допустимых символов URL, отменяя пробелы и другие спецсимволы, кодируя кириллицу, %, ?,
    UrlDecode(string);

    UrlDecode(string, System.IO.TextWriter)

    Раскодирует строку символов URL
    UrlTokenEncode(byte[ ]) Выполняет те же действия, что и UrlEncode(), но работает с байтовым массивом, который содержит данные в формате Base64
    UrlTokenDecode(string) Выполняет те же действия, что и UrlDecode(), но работает с байтовым массивом, который содержит данные в формате Base64
    MapPath(string) Возвращает физический файл, соответствующий определенному виртуальному пути файла на Web-сервере
    Transfer(string);

    Transfer(string, bool);

    Transfer(System.Web.IHttpHandler, bool)

    Передает исполнение другой Web-странице в текущем приложении. Этот метод похож на метод Response.Redirect(), но работает быстрее. Но его нельзя применять для перемещения пользователя на сайт, расположенный на другом Web-сервере, или же на страницу, отличную от ASP.NET (например, HTML или ASP)

    При использовании метода Server.Transfer() не происходит полного цикла. Вместо этого механизм ASP.NET просто загружает новую страницу и начинает ее обработку. В результате URL, отображаемый в броузере клиента, не меняется (...хотели кока, а съели Кука).

    Например:

    // Можно переместиться на файл в текущем Web-приложении
    Server.Transfer("newpage.aspx");
    ................................
    // Перемещаться на другой Web-сайт нельзя.
    // Это вызовет ошибку!!!
    Server.Transfer("http://www.prosetech.com");

    Метод Server.MapPath() является еще одним полезным методом. Пусть, например, мы собираемся загрузить файл под названием info.txt из текущего виртуального каталога. Вместо жесткого кодирования пути можно использовать Request.ApplicationPath(), чтобы получить текущий относительный виртуальный каталог. Затем применить метод Server.MapPath() - для преобразования его в абсолютный физический путь.

    Например:

    string physicalPath = Server.MapPath(Request.ApplicationPath 
    						+ "/info.txt");
    	
    // Открытие файла
    StreamReader reader = new StreamReader(physicalPath);
    // Что-то делаем
    reader.Close();

    Объект User

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

    Вернуться к учебному плану