Модель ASP.NET

196

Формы HTML

Тем, кто знаком с языком HTML, известно, что наиболее простым способом отправки клиентских данных серверу является использование дескриптора <form>. Внутри дескриптора <form> можно размещать дескрипторы <input>, представляющие другие базовые элементы пользовательского интерфейса, вроде кнопок, текстовых полей, окон списков и переключателей.

Например, ниже показана HTML-страница с формой, которая содержит два текстовых поля, два флажка и кнопку отправки, т.е. всего пять дескрипторов <input>:

<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
        <title>Опрос</title> 
    </head> 
    <body> 
        <form method="post" action="page.aspx"> 
            <div> 
                Введите ваше имя:  
                <input type="text" name="FirstName" /><br /> 
                Введите фамилию:  
                <input type="text" name="LastName" />
                <br /><br />
                На чем программируете:
                <br />   
                <input type="checkbox" name="CS" />C#
                <br />    
                <input type="checkbox" name="VB" />VB .NET 
                <br /><br /> 
                <input type="submit" value="Отправить" id="OK" /> 
            </div> 
        </form> 
    </body> 
</html> 

На рисунке показано, как эта простая страница будет выглядеть в окне веб-браузера:

Простая HTML-форма

Когда пользователь щелкает на кнопке Submit (Отправить), браузер извлекает текущие значения каждого элемента управления и формирует из них длинную строку. Затем эта строка отправляется странице, указанной в дескрипторе <form> (в данном случае page.aspx) с использованием HTTP-операции POST. В данном примере это означает, что веб-сервер может получить запрос со следующей строкой информации:

FirstName=Василий&LastName=Пупкин&CS=on&VB=on 

При создании этой строки браузер соблюдает определенные правила. Информация всегда отправляется в виде последовательности пар "имя-значение", разделенных символом амперсанда (&). Внутри пары имя отделяется от значения знаком равенства (=). Флажки игнорируются до тех пор, пока не будут выбраны, в таком случае браузер передает в качестве значения текст on. Полную информацию о стандарте форм HTML, поддерживаемом в каждом текущем браузере, можно найти на W3C - Forms.

Фактически все серверные каркасы программирования добавляют уровень абстракции к необработанным данным формы. Они разбивают эту строку и представляют ее более полезным способом. Например, JSP, ASP и ASP.NET позволяют извлекать значение элемента управления формы с использованием тонкого объектного уровня. В ASP и ASP.NET значения можно искать по имени в коллекции Request.Form. Если преобразовать предыдущую страницу в веб-форму ASP.NET, тогда этот подход можно будет применить с помощью такого кода:

string firstName = Request.Form["FirstName"]; 

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

string firstName = txtFirstName.Text;

Данный код также обладает преимуществом безопасности типов. Иначе говоря, при извлечении состояния флажка вы получите булевское значение true или false вместо слова on. В результате разработчики оказываются изолированными от индивидуальных особенностей синтаксиса HTML.

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

Динамический пользовательский интерфейс

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

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

string message = "<span style=\"color:Red\">Welcome " + 
                txtFirstName.Text + " " + txtLastName.Text + "</span>";
Response.Write(message);

С другой стороны, ситуация упрощается при определении в ASP.NET элемента управления Label (метка):

<asp:Label ID="Label1" runat="server"></asp:Label>

Теперь можно просто установить его свойства:

Label1.Text = "Welcome " + txtFirstName.Text + " " + txtLastName.Text;
Label1.ForeColor = Color.Red;

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

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

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

Модель событий ASP.NET

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

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

ASP.NET облегчает ситуацию, предлагая управляемую событиями модель обработки (event-driven model). В этой модели разработчик сначала добавляет в веб-форму элементы управления, а затем решает, на какие события он хочет реагировать. Каждый обработчик событий представляет собой отдельный метод, благодаря чему код страниц выглядит аккуратно и организованно.

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

  1. При первом запуске страницы ASP.NET создает объекты этой страницы и ее элементов управления. Далее выполняется код инициализации, после чего страница преобразуется в HTML и возвращается клиенту, а созданные объекты удаляются из памяти сервера.

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

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

  4. Далее ASP.NET проверяет, какая именно операция привела к обратной отправке данных, и генерирует соответствующие события (например, Button.Click), на которые разработчик может предусмотреть в своем коде определенную реакцию.

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

  5. Измененная страница преобразуется в HTML и возвращается клиенту. Объекты страницы удаляются из памяти. В случае если происходит еще одна обратная отправка данных, ASP.NET повторяет действия, перечисленные в пунктах 2-4.

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

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

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

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

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

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

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

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

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

Что собой представляет автоматическая обратная отправка

Чтобы использовать автоматическую обратную отправку, понадобится установить свойство AutoPostBack веб-элемента управления в true (по умолчанию это свойство устанавливается в false, что гарантирует оптимальную производительность в случаях, когда не требуется реагировать ни на какие события изменения). После этого ASP.NET использует клиентские возможности JavaScript для устранения пробела между клиентским и серверным кодом.

В частности, происходит следующее: в случае создания веб-страницы с одним или более веб-элементами управления, для которых настраивается AutoPostBack, ASP.NET добавляет в визуализируемую HTML-страницу JavaScript-функцию по имени ____doPostBack(). При вызове эта функция инициирует обратную отправку, отправляя страницу обратно веб-серверу со всеми данными формы.

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

<input type="hidden" name="__EVENTTARGET" id="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" id="__EVENTARGUMENT" value="" />

Функция ____doPostBack() отвечает за установку этих значений в соответствующую информацию о событии и последующую отправку формы. Ниже представлен пример функции ____doPostBack():

function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}

He забывайте, что ASP.NET генерирует функцию __doPostBack() автоматически. Этот код расширяется при добавлении к странице большего количества элементов управления AutoPostBack, поскольку данные о событиях должны быть установлены для каждого элемента управления.

Наконец, любой элемент управления, свойство AutoPostBack которого установлено в true, подключается к функции __doPostBack() с использованием атрибута onclick или onchange. Эти атрибуты указывают на то, какое действие следует выполнить браузеру в ответ на клиентские JavaScript-события onclick и onchange.

Другими словами, ASP.NET автоматически преобразует клиентское событие JavaScript в серверное событие ASP.NET, используя функцию __doPostBack() в качестве посредника. Опытный разработчик ASP-приложений смог бы, конечно, и вручную создать подобное решение для веб-страниц в традиционном стиле ASP. Но ASP.NET, несомненно, значительно облегчает жизнь разработчика тем, что обрабатывает все эти детали автоматически.

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

Последним компонентом в модели ASP.NET является механизм состояния представления (view state). Этот механизм решает еще одну проблему, которая возникает из-за того, что HTTP не поддерживает состояний — утрату информации об изменениях.

Каждый раз, когда страница отправляется обратно серверу, ASP.NET получает все данные, которые пользователь ввел в любом из содержащихся в дескрипторе <form> элементов управления <input>. После этого ASP.NET загружает веб-страницу в исходном состоянии (на основе схемы компоновки и параметров по умолчанию, которые были указаны в файле .aspx) и настраивает ее в соответствии с этими новыми данными.

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

Обычно проблема отсутствия поддержки состояний преодолевается за счет использования простых cookie-наборов, сеансовых cookie-наборов и других обходных приемов. Однако все такие механизмы требуют применения мер собственной разработки (на которые порой уходит немало усилий).

Для устранения этого ограничения в ASP.NET имеется свой интегрированный механизм сериализации состояния. По сути, этот механизм работает так: после завершения выполнения кода страницы (и непосредственно перед генерацией и отправкой клиенту окончательного документа HTML) ASP.NET изучает свойства всех представленных на этой странице элементов управления. Если хоть какое-то из этих свойств изменилось по сравнению с тем, каким оно было в исходном состоянии, ASP.NET делает соответствующую заметку в коллекции "имя-значение". Потом ASP.NET берет всю собранную информацию и сериализирует ее в строку формата Base64 (который гарантирует отсутствие специальных символов, являющихся недопустимыми в HTML), а затем вставляет эту строку в раздел <form> страницы как новое скрытое поле.

При следующей обратной отправке данной страницы, ASP.NET выполняет перечисленные ниже действия:

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

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

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

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

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

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

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

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

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

Обработка запросов страниц ASP.NET

Анализ состояния представления

Если вы просмотрите сгенерированный HTML-код для страницы ASP.NET, то обнаружите скрытое поле ввода с информацией о состоянии представления:

Скрытое поле состояния представления

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

// viewStateString содержит информацию состояния представления. 
string viewStateString = Request["__VIEWSTATE"];

// Преобразовать строку Base64 в обычный массив байт, 
// представляющих ASCII-символы. 
byte[] bytes = Convert.FromBase64String(viewStateString);

// Выполнить десериализацию и отобразить строку
Label1.Text = System.Text.Encoding.ASCII.GetString(bytes);

В расшифрованном виде строка с информацией о состоянии будет выглядеть примерно так:

?	915261182d__ControlsRequirePostBackKey__CSVB[?S???&?7 ???

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

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

Разбиение состояния представления

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

Для этого понадобится установить атрибут maxPageStateFieldLength элемента <pages> в файле web.config. Тем самым определяется максимальный размер состояния представления в байтах. Ниже показан пример, определения размера состояния представления в 1 Кбайт:

<configuration>
    <system.web>
      <compilation debug="true" targetFramework="4.0" />
      <pages maxPageStateFieldLength="1024" />
    </system.web>
</configuration>

Следует помнить, что разбиение состояния представления является просто механизмом минимизации проблем с определенными прокси-серверами (что является относительно редким явлением). Разбиение состояния представления не улучшает производительности (наоборот, оно привносит небольшие накладные расходы на дополнительную сериализацию). Для улучшения производительности следует стремиться к включению в состояние представления как можно меньшего объема информации.

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