Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Модуль 2. Разработка Web-приложений с помощью ASP.NET Введение До появления технологии ASP.NET процесс разработки Web-приложений довольно сильно отличался от разработки настольных приложений (desktop applications). Последние поддержаны множеством APIфункций, классов и инструментов студии. Программисты традиционных типов приложений приобрели навыки, которые значительно повысили эффективность их труда. К сожалению, эти навыки мало пригодны для создания содержимого (content) или реализации динамики поведения (behaviour) Web-страниц. Webпрограммирование требует преодоления множества особых барьеров: Знание языков гипертекста (markup languages), Умение писать скрипт на одном или нескольких языках (создавать scripting environments), Знание деталей процесса обмена информацией между клиентом и сервером, Учет особенностей платформы клиента и типа его браузера. Инновационная технология ASP.NET призвана покончить с таким положением вещей. Нельзя сказать, что в настоящем виде она выполнила это устремление, но определенные успехи есть. Теперь Web-программисты не должны думать о типе клиентского браузера. Они могут брать элементы управления с панели управления, размещать их на формах (Web Forms), затем методом двойного щелчка создавать функции реакций на сообщения, которые уведомляют о событиях, возбуждаемых элементами. Разработчики могут пользоваться всеми инструментами студии, которые давно применяются для разработки настольных приложений. Отметим, однако, что какие бы обещания не давали новые технологии и инструменты, доводка страницы с помощью тегов HTML остается главным оружием. Поэтому знание структуры HTML-документа, языка HTML и каскадных стилей CSS (Cascading Style Sheets) остаются критически важными элементами подготовки разработчика Web-приложений. Клиент-сервер Одним из самых важных моментов в понимании механизма работы Web-приложений является четкое представление о разделении труда, которое существует между компонентами процесса. Клиентский браузер и сервер представляют собой отдельные программы, обычно функционирующие на разных компьютерах и, возможно, в рамках различных операционных систем. Они обмениваются фрагментами данных, которые называются HTTP-сообщениями. Сообщение, отправляемое клиентом серверу, называют запросом (request), а сообщение, отправляемое сервером клиенту — откликом (response). Процесс обмена информацией инициирует клиент. Он вводит в адресную строку браузера адрес сервера и имя запрашиваемой страницы. В ответ на это сервер посылает клиенту информацию в виде статического HTML-текста и некоторых кодовых вставок (скрипта) на языке JScript. Клиентский браузер отображает информацию в рамках Web-формы (окна браузера). Клиент, взаимодействуя с формой, вновь посылает (posts back) информацию на сервер. Сервер опять отправляет информацию клиенту, и т. д. Последовательность таких операций осуществляет периодический обмен информации. Один цикл обмена обозначается термином round trip. Новизна подхода ASP.NET состоит в том, что ответная информация на лету (динамически) создается процессом ASP.NET. Генерируемая сервером информация автоматически учитывает непрерывно изменяющуюся ситуацию. Под изменением ситуации понимаются изменения во времени или состоянии. В результате взаимодействия сервера с пользователем изменяется состояние элементов управления Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 страницы, которое отражает изменения в данных. С течением времени изменяются цены на товары и услуги. Один и тот же запрос клиента завтра может вызвать не ту реакцию сервера, что сегодня. Эта реакция определяется логикой кода поддержки страницы и может зависеть от: Текущего состояния базы данных (заказ билетов, резервирование заказов, оплата услуг), Значения полей данных, введенных пользователем (регистрация клиента в базе данных сайта, или вычисление результата обращения к Web-сервису), Времени ввода данных (дистанционное обучение, резервирование заказов). Периодический обмен информации между клиентом и сервером (последовательность операций обращения к серверу и получения ответа) представляет собой последовательность циклов round trip. Обычно обмен провоцируется событием типа Click или SelectedIndexChanged. Многие элементы управления на Web-форме умеют возбуждать подобные события, а код поддержки страницы на сервере должен на них реагировать. Разделение труда между сервером и браузером Заметьте, что не каждое действие пользователя приводит к циклу обмена. Некоторые действия могут быть обработаны на стороне клиента, то есть самим браузером. Для этого предназначены кодовые вставки или скрипт, часто присутствующий в теле HTML-документа. Клиентский скрипт — это код, исполняемый браузером клиента в течение всего времени воспроизведения HTML-документа. Он обычно написан на Java Script — языке программирования, разработанном фирмой Netscape. Иногда этот язык называют ECMAScript. ECMA (European Computer Manufacturers Association) — ассоциация для выработки стандартов в системах коммуникации. Одним из утвержденных стандартов Java Script является ECMA-262. Язык выработан в соответствии с соглашением между Microsoft и Netscape. Термин JScript обозначает Microsoft-версию реализации ECMA-262, а JavaScript — версию Netscape. Заметим, что наиболее важные задачи все-таки решаются не на клиентской машине, а на сервере. А, следовательно, они требуют выполнения цикла обмена. Среди этих задач можно выделить: Надежную проверку правильности заполнения формы, Выборку затребованных клиентом данных из базы на сервере, Решение ресурсоемких задач с учетом данных, введенных пользователем. Память состояний Друим важным моментом в понимании логики функционирования Web-приложений является то, что обычно Web-сервер работает, не запоминая состояний элементов управления страницы. Завершив обработку страницы и послав клиенту HTML-текст, сервер освобождает все связанные с ней ресурсы (забывает ее). Это позволяет ему обрабатывать сотни тысяч запросов в день. Такой стиль работы обозначается термином stateless execution (отсутствие памяти состояний). Он обусловлен врожденными особенностями протокола обмена информацией TCP/IP. В традиционных Web-приложениях сервер получает ту информацию, которую ввел пользователь (она особым образом посылается на сервер). При возврате (postback), повторном отсыле клиенту той же страницы информация о состояниях элементов обычно теряется. Каждый цикл обмена данными (round trip) можно рассматривать, как отдельную пару вида "запрос-ответ" (Request, Response), никак не связанную с другими такими же парами. Однако часто логика Web-приложения требует помнить состояние элементов страницы, и это вызывает определенные проблемы. Технология ASP.NET дает несколько способов их решения. Упомянем один из них. В ASP.NET появились специальные серверные элементы управления, которые помечены атрибутом runat="server". Они помнят свои состояния, так как поддержаны кодом на стороне сервера. Сама Webстраница также поддержана на стороне сервера классом, производным от класса Page. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Состояния элементов запоминаются в скрытой (hidden) переменной __VIEWSTATE языка HTML. Эта переменная играет важную роль при обработке страницы на стороне сервера. Браузеры ее игнорируют, а ASP.NET-процесс использует ее, как записную книжку, что позволяет создать иллюзию памяти состояний. Код, работающий на стороне сервера, при генерирации нового HTML-контента учитывает как старые, так и новые (измененные пользователем) состояния элементов, записаные в переменной __VIEWSTATE. Это возможно лишь при повторных обращениях к серверу. Чтобы отличить первое обращение клиента от повторного, разработчик Web-приложения может использовать флаг IsPostBack, который имеется в классе Page. Во многих сценариях это свойство необходимо для эффективной работы с данными на стороне сервера. Кроме того, изменение состояния элементов на стороне клиента способно возбудить событие, которое запоминается и позже обрабатывается на стороне сервера. Эту модель называют отложенной обработкой событий. Особенностью серверных элементов является то, что по умолчанию они не возбуждают скоростных событий, например, onmousemove (движение мыши над объектом). Это объясняется логикой сценария работы системы. Обработка таких событий на стороне сервера потребовала бы недопустимых потерь времени. Однако, при необходимости разработчик сайта может добавить обработку таких событий на стороне клиента. Там обработка подобных событий не требует больших затрат, так как на них реагирует сам браузер и передача данных на сервер (round trip) отсутствует. В ASP.NET существует возможность генерировать (на стороне сервера) код скрипта, который позже будет выполняться браузером на стороне клиента. Такой прием называется генерацией динамического контента. Вы можете на лету генерировать как сами HTML-элементы, так и их поведение, то есть, код JScript, реагирующий на события, возбуждаемые элементами. Сервер можно сконфигурировать так, чтобы он кэшировал содержимое страниц. Это позволяет значительно повысить производительность работы сервера, так как многие клиенты запрашивают одни и те же страницы. Однако на начальном этапе изучения ASP.NET полезно считать, что информация о состояниях элементов после завершения обработки страницы на сервере теряется и вырабатывать логику своего кода с учетом этого фактора. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 1. Разработка Web-приложений 1.1. Клиентские и серверные технологии Многие дизайнеры Web-сайтов используют клиентские технологии, которые предполагают умение оперировать кодом на языках HTML и JavaScript, а также использовать каскадные стили CSS. HTMLдокументы, выполненные таким образом, сервер просто отправляет в клиентский браузер, не производя предварительно каких-либо преобразований. Серверные технологии (CGI, PHP, JSP, ColdFusion, ASP, ASP.NET, Ruby on Rails) отличаются тем, что они преобразуют исходные тексты (например, ASPX-файлы вместе с файлами кода поддержки на языке C#) в выходные HTML-документы. Последние и отправляются клиенту. Заметьте, что в обоих случаях клиентский браузер получает от сервера и интерпретирует HTML- и JScriptкод. Браузер не видит исходного (например, ASPX-) кода, работающего на сервере. ASPX-документ — это исходный материал, который используется сервером для генерации HTML-документа. Это действие (создание HTML-кода) выполняет ASP.NET-процесс — специальное приложение, работающее на сервере. Сервер IIS Если в вашей системе установлен IIS (Internet Information Services) — Windows-компонент, реализующий функциональность Web-сервера, то в ней существует понятие Default Web Site. Пользователи интрасети Avalon не имеют прав, достаточных для того, чтобы конфигурировать IIS. Поэтому (а также и по другим причинам) мы не будем работать с IIS. Вместо этого мы будем использовать сервер Кассини (см. ниже). Однако, чтобы получить представление о виртуальных директориях сервера IIS, прочтите этот параграф до конца. Каждый Web- или FTP-site должен иметь только одну домашнюю директорию (home directory). Она является неким центром, где должны размещаться публикуемые сервером страницы или файлы. По умолчанию ее роль выполняет: C:/Inetpub/wwwroot — для Web-сайта, C:/Inetpub/ftproot — для FTP-сайта. Для клиентов сайта адрес папки C:/Inetpub/wwwroot имеет вид http://ServerName, где ServerName — имя сервера. Имя сервера обычно совпадает с именем машины, но может быть другим (это зависит от настороек при установке IIS). При разработке Web-сайта на одной машине эта машина выполняет роль и клиента и сервера. В этом случае вместо имени сервера используют псевдоним http://localhost. Для размещения HTML-страницы на сервере (например, файла с именем FileName.htm) можно создать внутри директории wwwroot папку (например, DirName), поместить в нее файл FileName.htm, и после этого он будет доступен любому клиенту, который обратится к серверу, с помощью адреса: http://ServerName/DirName/FileName.htm Адрес задается в адресном поле браузера. Запрашиваемый клиентом ресурс имеет вид универсального идентификатора (Universal Resource Identifier—URI). Он может описывать местонахождение (URL — Locator) или имя (URN — Name) ресурса. Uniform Resource Locator — это обобщение понятия адреса Web-документа. URL обозначает ссылку на обобщенный ресурс (объект) информационной системы Чтобы не засорять папку wwwroot, называемую IIS Home Directory, можно (а в учебных целях и нужно), поступать по-другому — размещать файлы в произвольной папке файловой системы и ассоциировать ее с так называемой виртуальной директорией (Virtual Directory) сайта. Клиентские браузеры ничего не знают о реальной структуре дискового пространства сервера, а виртуальные папки нужны для того, чтобы создать и поддерживать иллюзию файловых адресов на сервере. Говорят, что Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 виртуальная директория является псевдонимом (alias) реальной папки на диске или отображается (mapped) на нее. Опишем процедуру создания виртуальной папки. Вы можете выполнить ее на своем компьютере, если в нем установлен компонент IIS. Создайте папку (например, D:\Web), в которой будут хранится ваши Web-приложения. Запустите менеджер IIS (LWin+R и команда inetmgr). В окне менеджера найдите узел с именем сервера, а в нем узлы Web Sites и Default Web Site. То, что вы видите в правом окне, является содержимым Home Directory вашего сайта. В контекстном меню узла Default Web Site выберите команду New→Virtual Directory. При этом будет запущен мастер создания виртуальной папки. Нажмите кнопку Next и введите имя (точнее, псевдоним — ailias) виртуальной папки: MyWeb. Нажмите кнопку Next, затем кнопку Browse и укажите реальную папку D:\Web, в которой будут хранится Web-приложения и которая будет соответствовать виртуальной папке MyWeb. Нажмите кнопку Next и установите флаги допустимых операций: Read, Run scripts. Нажмите кнопки Next и Finish. В указанной папке вы будете размещать файлы вашего сайта. Клиенты будут обращаться к ним по адресу: http://ServerName/MyWeb/<имя файла>. Этот адрес отображается на D:\Web\<имя файла>. Сервер Кассини Для создания файлов Web-приложений можно пользоваться любым текстовым редактором (такие мазохистские наклонности удивительно живучи), но неизмеримо более удобно разрабатывать их в рамках Visual Studio. Ниже предполагается, что вы работаете в Visual Studio 2008. Если разработка ведется в студии VS2005 (платформа .NET 2.0) или VS2008 (платформа .NET 3.5), то при запуске Web-приложений в них автоматически запускается простой сервер ASP.NET Development Server (иногда его называют сервером Cassini), который работает без помощи службы IIS, что сильно упрощает процесс разработки и отладки Webприложений. Этот сервер выполняет лишь некоторые функции IIS, но их вполне достаточно для разработки и отладки Web-приложений всех типов. После отладки приложение можно разместить на реальном сервере. Его можно запускать как под управлением IIS, так и под управлением сервера Cassini. Ниже мы предполагаем, что вы не пользуетесь услугами IIS, а работаете с сервером Cassini. Он автоматически создает виртуальный адрес. Это означает, что в каком бы месте файловой системы не размещалась реальная папка MySite с реальным файлом MyFile, его адрес будет иметь вид: http://localhost:1037/MySite/MyFile Номер порта (1037) выбирается сервером Cassini и он может иметь другое значение. Большая часть примеров этого учебника не пользуется виртуальными папками IIS. Проба пера Обычно Web-сайт ASP.NET представляет собой множество каталогов и файлов с различными расширениями. В нем могут быть как HTML-файлы, так и файлы ASPX. Кроме того, сайт содержит множество вспомогательных файлов, которые следует размещать в специальные папки App_Code, App_Data и другие. Изучая курс ASP.NET, мы будем создавать множество страниц того и другого типа, а также множество службных файлов. Откройте студию и выполните следующие действия. Создайте новый Web Site (File→New→Web Site). Тщательно выберите местоположение папки сайта не в виртуальной папке IIS, а в файловой системе. Для этого в диалоге New Web Site, в списке Location, выберите значение File System, а не HTTP. В списке справа задайте файловый путь. Вы должны иметь полный набор прав (чтение и запись) на указанную папку. Дополните файловый путь именем сайта — MySite и нажмите кнопку OK. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.1. Настройки мастера при создании нового сайта Добавьте к сайту новую папку с именем HTML. Для этого дайте команду New Folder в контекстном меню сайта (ищите имя сайта в окне Solution Explorer). Добавьте в эту папку новый HTML-документ (Add New Item→HTML Page). Назовите его: First.htm. Вы увидите заготовку htm-файла. Она открыта в специализированном редакторе HTML-документов, который может работать в двух режимах: Design и Source. В нижней части окна редактора нажмите кнопку Design или Source. В режиме Source вы видите код заготовки HTML-документа. Редактор переключился в режим изменения кода, работают подсказки (механизм под именем IntelliSense). <!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><title>Untitled Page</title></head> <body></body> </html> Откройте окно Properties (View→Properties Window) и убедитесь, что оно отображает свойства документа (DOCUMENT). Просмотрите и проанализируйте содержимое выпадающего списка окна Properties. Если задаться целью создать самый простой, но работающий HTML-документ, то можно уничтожить весь текст начальной заготовки и ввести вместо него неразмеченный текст: Hello from HTML-документ! В этом, неразмеченном тексте отсутствует строка пролога (<!DOCTYPE...), определяющего тип документа, тег <html>, заголовок <head> и тело <body> документа, которые были в первоначальной заготовке. Браузер терпит нарушения стандарта (до определенной степени), поэтому мы не будем исправлять документ, а проверим его в работе. Информацию о прологе HTML-документа, который мы бесцеремонно выбросили, вы можете получить в статье MSDN (Web Pages: A Programmer's Perspective). Там же прочтите и о мета-данных (содержимое тегов <meta>). Далее мы не будем концентрировать внимание на менее важных деталях. Они отнимают много времени, рассеивают внимание, могут скрыть логику работы всей системы, а также логику преобразований документов ASP.NET на сервере. Запустите приложение (точнее, HTML-документ First.htm). Это можно сделать тремя разными способами. Выбрать в контекстном меню документа First.htm команду View in Browser. Запустить проект (Ctrl+F5). Если студия не настроена, то предварительно можно выбрать в контекстном меню документа First.htm команду Set As Start Page. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Произвести двойной щелчок по документу (First.htm) в рамках любого файлового менеджера, например, Windows Explorer. В первых двух случаях происходит имитация обмена между сервером и клиентом. На самом деле один компьютер выполняет обе роли. Автоматически запускается вспомогательный сервер ASP.Net Development Server, который отправляет документ в браузер. В третьем случае сервер вообще не работает, так как операционная система (по ассоциации расширения .htm) сама направляет файл в браузер. Сервер Cassini Встроенный сервер ASP.NET Development Server впервые был представлен в виде примера (sample) с открытым кодом. Он известен под именем сервера Cassini. Его исходный код доступен на сайте http://www.asp.net. Если ASP.NET Development Server работает, то он проявляет себя в System Tray. Отыщите его там и выберите из контекстного меню команду Show Details. Сервер откроет окно такого вида. Рис. 1.2. Информационное окно сервера Кассини Страница по умолчанию Задавая в адресной панели браузера какой-либо адрес, например: www.microsoft.com, мы на самом деле обращаемся к единственному файлу, размещенному на сервере Microsoft. Имя файла вычисляется по некоторому правилу, определяемому установками сервера. Этот файл называется файлом по умолчанию, он-то и представляет собой код домашней страницы (home page). Обычно домашняя страница содержит ссылки на другие документы сайта. Наиболее часто файлом home page является Default.htm, но им может быть и любой другой документ (например, Default.aspx, index.htm, или iisstart.asp). В нашем случае файлом по умолчанию является Default.aspx. Он был создан студией при открытии нового проекта (сайта). В настоящий момент этот файл представляет собой пустую заготовку, которую надо развить так, чтобы она выражала облик стартовой страницы нашего сайта. Страницей по умолчанию (Default Page) называется та страница, которая представляется посетителю вашего сайта, если в адресной строке он не указал никакой конкретной страницы, а задал лишь адрес сайта (http://localhost:1037/MySite/). Номер порта (1037) в вашем случае может быть другим. Обычно такой странице дают имя Default.aspx и хранят ее в корневой папке сайта. Ссылки, расположенные на странице Default.aspx, помогают пользователю осуществлять навигацию по всему сайту. Идентификатор localhost — это псевдоним сервера. Если вы знаете имя своего сервера (обычно оно совпадает с именем компьютера), и работаете под управлением IIS, то можете заменить в строке адреса localhost на имяСервера и результат будет тем же. Кроме того, псевдоним localhost эквивалентен IP-адресу 127.0.0.1. Это адрес отражающей заглушки (драйвера loopback adapter). Можно заменить localhost на 127.0.0.1, и адрес будет http://127.0.0.1:1037/MySite/. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Страница по умолчанию задается как свойство проекта. Во время разработки Web-приложений удобно изменить тактику выбора этой страницы. В окне Solution Explorer выделите строку с именем проекта и дайте команду Shift+F4, или выберите команду View→Property Pages из меню главного окна, или выберите ее из контекстного меню проекта. Откроется диалог Property Pages. Имя страницы по умолчанию можно изменить в окне диалога, открываемого с помощью закладки Start Options. Если вы установите переключатель Start Action в положение Use current page, то страницей по умолчанию будет та страница, которая активна в данный момент. Это удобно при отладке приложений сайта. Логику страницы Default вы разовьете позже, а сейчас внесем код разметки в документ First.htm. 1.2. Код разметки Текст кода разметки приведен ниже. Вы можете просто скопировать фрагмент текста настоящего Wordдокумента и через буфер обмена перенести его в First.htm. Копируя и перенося код в окно редактора студии (особенно в документах ASPX), следите за значениями идентификаторов элементов управления. Студия защищает идентификаторы и имеет привычку вносить свои коррективы, например, вместо значения id="myID" она может вставить id="Text1". В нашем документе пока нет идентификаторов, но потом они будут и вы должны быть готовы к подобным проделкам редактора студии. Вот новый текст документа First.htm. <!-- Пролог HTML-документа --> <!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"> <!-- Заголовок HTML-документа --> <head> <title>First</title> </head> <!-- Тело HTML-документа --> <body> <!-- Панель div - это контейнер HTML-элементов --> <div> <h4>&laquo;HTML-элемент <big>Anchor</big> &mdash; это гиперссылка&raquo;</h4> <!-- Параграф имеет атрибут стиля --> <p style="font-family:Verdana;"> Некоторые элементы не требуют завершающего тега: &lt;br&gt; (line break), &lt;input&gt; (поле ввода).<br /> Однако, новый стандарт XHTML требует завершать все теги. Поэтому следует использовать такие версии указанных тегов: &lt;br/&gt;, &lt;input/&gt; </p> <p> <!-- Гиперссылка ссылается на другой документ Details.htm --> <a href="Details.htm">Подробнее о гиперссылках</a> </p> </div> </body> </html> Запустите приложение и убедитесь, что оно производит такой результат. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.3. Окно браузера, воспроизводящего HTML-документ с гиперссылкой Анализ документа HTML Произведем анализ первого HTML-документа. Два тега и часть документа, между ними, образуют блок, называемый HTML-элементом. Например, <a href="Details.htm">Подробнее о гиперссылках</a> Этот элемент иллюстрирует самое главное понятие гипертекстовых языков — гиперссылку (hyperlink), которая является ничем иным, как переходом. Оператор перехода должен быть знаком вам, так как он существует во всех языках программирования. Для создания гиперссылок используется тег <a> языка HTML. Он является сокращением слова anchor (которое порождает следующую ассоциативную связь: anchor→якорь→зацепка→связь→link→ссылка). Наша зацепка имеет атрибут href, значение которого знаменует ссылку на другой файл (href="Details.htm"). Некоторые элементы не требуют завершающего тега. Например: <br> (line break), <input> (input controls), или <hr> (horizontal rule) и вы увидите множество документов, где их нет. Однако, новый стандарт XHTML требует завершать все теги, поэтому лучше использовать такие версии указанных тегов: <br />, <input />, <hr />. Элемент HTML-страницы (или, тег) <p> — параграф (абзац) относится к множеству тегов, которые всегда требуют явного завершения. Управляющий символ & (ampersand) позволяет выводить (Character Entities) служебные символы, или символы, не входящие в множество ASCII. Например, для того, чтобы вывести французскую букву á, надо использовать комбинацию: &aacute; для вывода угловых скобок &lt; и &gt;. Задайте и просмотрите другие служебные символы, например: &spades; &oslash; &fnof; &#402; &int; &rarr; &uarr; &dagger; &frac14; &frac34; &clubs; &Delta; &infin; &asymp; &hearts; Язык HTML однопроходовый, в нем не может быть циклов, условий и переходов. Браузер просто читает HTML-файл и воспроизводит HTML-документ, но... вспомните определение гиперссылки. Продолжая логику нашей ссылки, добавьте в папку HTML новый файл Details.htm и введите в него такой код: <!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><title>Details</title> <style type="text/css">p strong { text-decoration: underline; }</style> </head> <body style="font-family:Arial"> <div> <h4>В <bdo dir="rtl">TEN.PSA</bdo> класс <em>HTMLAnchor</em> из пространства System.Web.UI.HtmlControls позволяет создать как обычную гиперссылку, так и реакцию на нажатие левой кнопки мыши в виде собственного программного события.</h4> <blockquote>При этом используется встроенное событие ServerClick.</blockquote> <p style="text-align:right;position:relative;right:37px;"> <strong><acronym title="Active Server Pages">ASP</acronym></strong></p> Блок &lt;style&gt; определяет CSS-стиль. Стиль применяется к выбранным элементам Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 HTML-документа. Выбор элементов производится с помощью селектора. Например, следующий селектор выбирает все абзацы и говорит, что текст внутри них должен стать подчеркнутым. <pre> p { text-decoration: underline; } </pre> Рассмотрим блок с более сложным селектором (<i>decendant selector</i>). <pre> &lt;style type="text/css"&gt; p strong { text-decoration: underline; } &lt;/style&gt; </pre> Этот селектор действует на все слова внутри пары тегов &lt;strong&gt; &lt;/strong&gt;, но только если они помещены внутрь абзаца &lt;p&gt;&lt;/p&gt;. Это <strong>слово</strong> не подчеркнуто, так как оно не вложено в тег &lt;p&gt;. <p>А это <strong>слово</strong> расположено внутри параграфа (is decendant of &lt;p&gt;). Поэтому оно принимает CSS-стиль, предписанный блоком &lt;style&gt;. См. также acronim ASP (выше).</p> <p><a href="First.htm">Назад</a></p> </div></body></html> Страница Details.htm в браузере Internet Explorer имеет следующий вид. Рис. 1.4. Облик страницы Details.htm Запустите проект (Ctrl+F5). При этом автоматически запускается отладочный сервер ASP.NET Development Server, который запускает браузер Internet Explorer для воспроизведения стартового документа. Управляя гиперссылками, произведите анализ поведения связки из двух страниц. Следите за тем, как изменяется поле адреса Internet Explorer. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Вы можете в адресной строке браузера вручную изменить адрес воспроизводимого документа (не нарушая принятый формат) и браузер послушно отобразит выбранный вами документ. Заметьте, что при запуске приложения в рамках IIS (мы пока этого не делаем, так как пользуемся услугами сервера Cassini) вместо псевдонима localhost можно использовать настоящее имя сервера (по умолчанию оно совпадает с именем машины). Обзор кода Название тега <em> происходит от английского emphasis — акцент, постановка ударения. Тег <bdo> происходит от bidirectional, он означает смену направления текста (в нашем случае rtl — right to left) в алгоритме реверсирования Unicode-текста. Для того, чтобы понять, как работает тег <acronym> совместно с атрибутом title, после запуска страницы наведите курсор мыши на текст ASP и подождите 5 секунд. Браузер покажет расшифровку акронима (аббревиатуры) ASP (Active Server Pages). Просмотрите справку MSDN по другим HTML-элементам, смысл которых вам не ясен. Блок <style></style> определяет каскадный стиль, принцип работы которого пояснен в тексте документа. Если вы впервые создаете html-документы, то сейчас, вероятно, ощущаете давление новых реалий, незнакомых понятий и терминов. Да, обилие тегов HTML и его синтаксис довольно сильно нагружают нашу память и снижают оптимизм и уверенность в быстром успехе. Мы видим, что создание приличного интерфейса требует значительных усилий рутинного характера и предполагает наличие опыта. Этот факт тем более должен повысить вашу оценку возможностей технологии ASP.NET, которая берет на себя значительную часть рутинной работы. В следующем разделе мы намерены показать другой подход к разработке Web-страницы. Он полярен рассмотренному выше, в том смысле, что код разметки автоматически генерируется дизайнером, мы за ним не следим, и концентрируем внимание лишь на реализации логики поведения страницы. Процесс разработки сайтов в реальных условиях требует смешанного подхода. Вы должны уметь использовать мощные инструменты студии и, в то же время, обладать филигранной техникой доводки контента с помощью тегов HTML. Наибольшие усилия, на мой взгляд, требуются при разработке кода на языке JScript, без которого трудно достичь необходимой динамики поведения Web-страницы на стороне клиента. Поведение на стороне клиента Вы, конечно, обратили внимание на то, что кроме операций перехода по гиперссылкам, наши страницы не обладают поведением, они — статичны. Для того, чтобы показать, как скрипт на стороне клиента (код JScript) влияет на динамику поведения страницы, создайте еще один документ по имени Dynamic.htm и добавьте его в папку HTML нашего сайта. Замените код этого документа на тот, что приведен ниже. <!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> <title>Dynamic</title> <style type="text/css"> body { font-family:Comic Sans MS; } .big { color:Red; font-size:xx-large; } .small { color:Blue; font-size:smaller; } </style> </head> <body><div> <span class="small" id="sp" onmouseover="className='big'; innerText='Java Script разработан компанией Netscape'" onmouseout="className='small'; innerText= 'JScript — это Microsoft-версия стандарта ECMA-262, а JavaScript — версия Netscape'"> Клиентский скрипт (JScript) не следует путать с серверным (C#) </span><br /> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <p> <a onclick="sp.innerText='Клиентский скрипт (JScript) не следует путать с серверным (C#)'">Reset</a> </p> </div></body> </html> Запустите приложение, сделав (если необходимо) этот документ стартовым. Убедитесь, что облик документа (рис. 1.5) изменяется при наведении курсрора мыши на строку текста. Текст элемента span (контейнера текста и изображений) изменяется при входе и выходе мышиного фокуса из его пределов. При нажатии на гиперссылку Reset текст возвращается в исходное состояние. Атрибуты: onmouseover, onmouseout и onclick — это события, реакции на которые содержатся в правой части операций присвоения. Значения атрибутов представляют собой код JScript, который изменяет свойства контейнера <span> с идентификатором sp. Обратите внимание на тот факт, что идентификатор sp необходимо указывать при управлении чужим элементом (см. код <a onclick="sp.innerText=...">Reset</a>) и он не нужен при управлении свойствами своего (или this) элемента (см. onmouseover="className='big'; . . .). В этом фрагменте className и this.className эквивалентны. Везде далее я не буду без надобности употреблять ссылку this. Гиперссылка <a> управляет свойствами чужого элемента (span), а обработчики мышиных событий элемента <span> управляют своими собственными свойствами. Свойство className изменяет именованный каскадный стиль (сами стили определены в заголовке документа <head>), а свойство innerText изменяет текст, вложенный в тег <span>. Подробнее о свойствах и атрибутах HTML-элементов смотри в учебнике Модуль 1. Введение в HTML и JScript. Рис. 1.5. Реакции на события onmouseover, onmouseout Порядок чтения Как было сказано ранее, знание синтаксиса языков HTML и JScript, а также структуры HTML-документа остаются критически важными элементами подготовки разработчика Web-приложений. Поэтому необходимо преодолеть соблазн быстрого входа в технологию полуавтоматического создания документов ASP.NET с помощью дизайнера студии и уделить некоторое время на изучение основ HTML, CSS и JScript. Детали разработки HTML и JScript-кода приведены в Модуле 1. Порядок чтения вы должны определить сами. Если вы знакомы с синтаксисом HTML и умеете разрабатывать код на языке JScript, то перейдите к следующему параграфу и обращайтесь к Модулю 1 по мере надобности. Если — нет, то рекомендую перейти к чтению Модуля 1 и вернуться сюда, когда вы почувствуете некоторую уверенность в создании обычных HTML-страниц. Следующий раздел посвящен разработке страницы ASP.NET. 1.3. Документ ASPX Работая в студии со страницами ASP.NET, вы можете автоматизировать процесс решения многих типовых задач. Это делается с помощью инструментов студии, называемых мастерами (Wizards). Мастера способны Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 генерировать код, который работает с множеством классов семейства ASP.NET, ускоряющих процесс проектирования страницы. Сейчас в рамках новой страницы ASPX мы продемонстрируем некоторые приемы быстрого проектирования (RAD — Rapid Application Development), когда включены основные инструменты студии, ускоряющие процесс разработки Web-приложений. Предлагаемый здесь материал носит демонстрационный характер, поэтому не пытайтесь сразу понять все детали, оцените лишь уровень, на котором ведется разработка. Если эта задача покажется вам слишком сложной, то пропустите весь раздел и перейдите к разделу 1.4., уровень сложности которого значительно ниже. Начиная с 1.4. и далее, мы постепенно будем входить в детали технологии ASP.NET. Итак, здесь мы не будем иметь дело с кодом разметки Web-страницы — его будут генерировать мастера студии. Нам надо лишь последовательно реализовывать логику запроса данных, отображения их в серверных элементах и синхронизации элементов управления при перемещении по строкам отображаемой таблицы (навигации пользователя по данным таблицы). Шаблоны проектов студии для создания Web Form на основе технологии ASP.NET рассчитаны на так называемую двухфайловую модель разработки: ASPX-файл описывает облик страницы, а связанный с ним CS-файл (code-behind) программную логику, или поведение страницы. Так, в проекте уже существуют два файла, поддерживающие страницу Default. Это — Default.aspx и Default.aspx.cs. Рассмотрим процесс создания подобной (двухфайловой) страницы, которая отображает данные таблицы заказов (Orders) базы данных NorthWind. Установки SQL Server Если вы успешно прошли через процедуру установки студии, то она установила SQL Server Express, и импортировала в него учебные базы данных: NorthWind и Pubs. В этом случае пропустите этот параграф Если вы работаете в классе, где есть проблемы с использованием SQL Server 2005 (или SQL Server Express), то используйте Access-аналоги учебных баз: NorthWind.mdb и Pubs.mdb. Везде заменяйте mdf на mdb и SqlDataSource на AccessDataSource. Более предпочтительно работать с SQL Server. Приложения ASP.NET выполняются от имени фиктивного пользователя Имя машины\ASPNET. Он обладает в рамках сервера необходимым уровнем прав. Обычно эти права установлены и работают по умолчанию. Если это не так, то надо обратиться к администратору сети. Если прав не хватает, то вас ждет сообщение: Cannot open database "Northwind" requested by the login. The login failed. Login failed for user /Имя фиктивного пользователя/. На домашней машине вы, возможно, сумеете добиться результата, установив флажок sysadmin в диалоге Login Properties, открытом для фиктивного пользователя. Чтобы добраться до этого флажка, надо: Открыть SQL Server Managment Studio и соединиться с сервером. В окне Object Browser выбрать узел Имя сервера→Security→Logins. В окне Summary→Logins найти имя машины\ASPNET). Если такого имени нет, то его надо добавить с помощью диалога New Login. Открыть контекстное меню для указанного объекта и выбрать в нем команду Properties. В открывшемся диалоге Login Properties выбрать узел Server Roles и установить флажок sysadmin. Такая мера считается довольно радикальной, может быть даже опасной. Она действует на все базы данных, зарегистрированные на сервере. Существует более мягкое решение. В диалоге Login Properties надо выбрать User Mappings, в окне Users Mapped to this login: выбрать базу данных (например, pubs) и в окне Database roles membership for: pubs установить флажок db_owner. Повторите эту процедуру для всех баз данных, с которыми вы собираетесь работать. Выполните ее хотя бы для двух учебных баз: NorthWind и Pubs. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Источник данных Далее мы собираемся работать с учебной базой данных NorthWind.mdf типа SQL Server Express. Microsoft SQL Server Express — это среда управления базами данных, которая уже установлена вместе со студией VS 2005 или VS 2008. Тип источника данных носит имя .NET Framework Data Provider for SQL Server (поставщик данных для SQL Server). Он может работать как с зарегистрированными на сервере источниками данных, так и с файлами отдельных баз данных. Файлы имеют расширение mdf. Поставщик данных (сам SQL Server Express) запущен в виде Windows-сервиса, выполненного в рамках технологии COM. Работа с капризными сервисами и скрытыми сущностями (объекты COM) — не самая лучшая новость от Microsoft для новичков, но я надеюсь, что вы корректно установили студию и нужный сервис запущен и работает исправно. Рекомендую проверить, запущен-ли он. Запустите утилиту конфигурации сервисов. Для этого дайте команду services.msc, либо с помощью команды (StartRun), либо в окне командной строки (StartRuncmd). В окне Services (открытом в рамках Microsoft Management Console) найдите сервис SQL Server (SQLEXPRESS) и выберите в его контекстном меню команду Start. В принципе вы имете возможность соединяться с файлами: SQL Server Compact 3.5 (.sdf), SQL Server и SQL Server Express (.mdf), и Microsoft Access (.mdb). Соединение непосредственно с файлами, а не с зарегистрированными источниками SQL Server, позволяет размещать базы данных вместе с разрабатываемым приложением и в некоторых случаях обойти процедуру установки SQL Server на машине клиента, то есть на машине, где размещено Web- или Windowsприложение. В состав SQL Server и/или SQL Server Express обычно входит учебная база Northwind.mdf. Она содержит данные фиктивной торговой компании, которая по давней традиции Microsoft служит полигоном для демонстрации возможностей технологии ADO.NET, а теперь и технологии LINQ (Language Integrated Query). Мы можем добавить в проект существующую базу Northwind или создать новую, произвольную базу данных. Все операции подобного типа выполняются мастерами студии. Файл с базой Northwind вы найдете в папке с материалами курса. Вы также можете добыть ее на сайте Microsoft. Отыщите файл Northwind.mdf, и приготовьтесь поместить его копию в специальную папку (App_Data) нашего проекта. Специальные папки приложения В подсистеме ASP.NET для папок со специальным содержимым принято использовать определенные имена. Она автоматически распознает следующие папки. Имя папки Описание содержимого App_Browsers XML-файлы с описанием свойств браузера (файлы с расширением .browser) App_Code Файлы с исходным кодом (.cs, .jsl), который компилируется и становится частью Webприложения. Здесь располагается код, работающий при первом обращении к приложению, он вновь компилируется только при обнаружении изменений. App_Data Файлы с локальными данными приложения (например .xml, или файлы баз данных: .mdb, .mdf). App_GlobalResources Файлы с ресурсами (.resx, .resources), которые компилируются в сборки с глобальным доступом (global scope). App_LocalResources Файлы с ресурсами, ассоциированными с определенными страницами, пользовательскими элементами (user controls) или мастер-страницей (master page). App_Themes Вспомогательные файлы (.skin, .css, .jpg), которые определяют облик ASP.NET Web-страницы или элемента. App_WebReferences Файлы ссылок на контракты (.wsdl), схемы (.xsd) и файлы обнаружения Web-сервисов (.disco,.discomap). Bin Файлы готовых (скомпилированных) сборок (.dll) элементов, компонентов и другого кода, используемого в приложении. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Кофигурацию сайта (регулируемые установки, действующие на этапе выполнения приложения) можно изменять с помощью XML-файла web.config, расположенного в корневой папке приложения (сайта). Файлы с таким же именем могут присутствовать в каждой из папок, указанных выше. Они служат для управления параметрами, влияющими на способ использования файлов данной папки. Например, процедура конфигурации позволяет ограничить доступ к папкам и файлам на основе индивидуальных прав или ролей (групп) пользователей. Если в проекте еще нет папки App_Data, то создайте ее с помощью команды Add ASP.NET Folder контекстного меню. Файл конфигурации проекта web.config, скорее всего, отсутствует в проекте, но он будет создан автоматически, как только вы запустите проект в режиме отладки (F5). Сделайте это, не откладывая, — файл конфигурации нам вскоре понадобится. Отображение данных таблиц SQL Server Express Сейчас мы покажем, как с помощью мастера студии создать соединение с базой данных и показать данные произвольной таблицы в рамках специального элемента управления на Web-странице. Добавьте в проект папку с именем DBPages (команда New Folder в контекстном меню проекта). В эту папку вставьте новую страницу TestDB (команда Add New ItemWeb Form). Проследите за тем, чтобы был установлен флажок Place code in separate file, который включает двухфайловую модель разработки Web-форм. Рис. 1.6. Структура папок Web-сайта Окно Solution Explorer отображает структуру и текущее состояние проекта. Убедитесь, что оно имеет вид, который показан на рис 1.6. Перетащите из окна Windows Explorer копию файла Northwind.mdf в папку App_Data. В меню View выберите команду Server Explorer. В окне Server Explorer найдите узел Data Connections и в его контекстном меню выберите команду Add Connection. Откроется диалог Add Connection, или Choose Data Source. Если открылся диалог Add Connection, то для практики нажмите кнопку Change и при этом откроется диалог Choose Data Source. В диалоге Choose Data Source, выберите тип источника Microsoft SQL Server Database File, и нажмите OK. При этом откроется диалог Add Connection. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.7. Окно мастера для создания соединения с базой данных В окно Database file name необходимо ввести путь к файлу с базой данных. Сделайте это с помощью кноки Browse. Нужный файл отыщите в папке App_Data. Установите способ аутентификации пользователя. Выберите Use Windows Authentication. Это упрощает работу с БД (устраняет необходимость вводить пароль). Нажмите кнопку Test Connection, чтобы убедиться в надежности соединения с базой Northwind. Нажмите кнопку OK. После этого соединение с базой (объект типа DataConnection) попадает в список соединений Server Explorer. Рис. 1.8. Окно инструмента студии Server Explorer Переведите страницу TestDB в режим Design (иначе мастер не включится в работу). В окне Server Explorer раскройте Tables, найдите таблицу Orders и оттащите ее на форму. После некоторой паузы, в течение которой мастер генерирует код разметки страницы, вы увидите на Webформе таблицу Orders. Она имеет вид, показанный на рис. 1.9. Диалог в правой части окна позволяет произвести типовые настройки элемента GridView, который был автоматически создан и вставлен в Web-форму мастером студии. Вы можете закрыть этот диалог (Esc), и вызвать его снова, нажав так называемую умную кнопку (Smart Tag). Отыщите эту маленькую кнопку в правом верхнем углу GridView и нажмите ее несколько раз. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.9. Элемент GridView в режиме дизайна Как видите, мастер вставок объектов произвел значительную работу. Нажмите кнопку Split внизу окна редактора студии для того, чтобы оценить ее объем. Во второй части расщепленного окна редактора вы видите код разметки, сгенерированный мастером. Убедитесь, что он вставил два объекта. <asp:GridView> — служит для отображения данных таблицы заказов (Orders), <asp:SqlDataSource> — инкапсулирует рутинные операции соединения с базой данных, чтения ее данных, а также реализует механизм привязки элемента отображения к данным (DataBinding). Первый относится к категории Data-Bound Controls, а второй — Data Source Controls. C помощью Intellisense рассмотрите атрибуты элемента SqlDataSource. Обратите внимание на то, что кроме команды выборки данных (Select), он имеет еще три команды управления данными (Insert, Delete и Update) Запустите проект и убедитесь, что страницы TestDB отображает данные таблицы. Заметьте, что мы не разрабатывали код. Мы лишь установили свойства новых элементов в режиме дизайна. Все элементы страницы с префиксом asp относятся к категории server controls, они (в отличие от обычных HTML-элементов) обладают способностью помнить свои состояния во время повторяющихся циклов обмена данными с сервером. Перечислим серверные элементы вида Data Source Controls: SqlDataSource, AccessDataSource, LinqDataSource, XmlDataSource, SiteMapDataSource, ObjectDataSource. Для отображения и управления данными с помощью механизма DataBinding используют другие серверные элементы (c префиксом asp). Они относятся к категории Data-Bound Controls. Перечислим их: DataGrid, DetailsView, RadioButtonList, DropDownList, CheckBoxList, BulletedList, DataList, ListBox, Menu, AdRotator, Repeater, TreeView. Конфигурация источника данных Выбранный автоматом (мастером вставок) способ отображения данных нельзя назвать удачным (мы видим слишком много данных: все заказы всех клиентов), поэтому покажем, как с помощью другого мастера (конфигурации источника данных) выстроить иной способ отображения данных. Сначала исследуем механизм DataBinding (автоматической привязки GridView к источнику данных SqlDataSource) и скорректируем его свойства. В окне дизайнера выделите SqlDataSource и в окне его свойств задайте более осмысленное имя (ID) = dsOrders. Таким же образом переименуйте элемент GridView, установите его свойство (ID) = grid. Связь GridView с источником данных поддерживается свойством DataSourceID. Замените его значение на dsOrders. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Для элемента GridView установите переключатель AutoGenerateColumns в положение true. На нижней панели управления дизайнера найдите элемент <form#form1>, выделите его, затем перейдите в окно свойств и отыщите свойство Style. В правой колонке списка (в строке Style) появится кнопка ellipsis button (...), которая открывает диалог коррекции стилей. Нажмите ее и задайте компонеты стиля, как показано на рис. 1.10. Рис. 1.10. Диалог управления атрибутами стиля Нажмите кнопку Smart Tag настройки элемента GridView и выберите команду Auto Format. В одноименном диалоге выберите цветовую схему, например: Simple. Мы настроили GridView, теперь изменим конфигурацию источника данных. В диалоге конфигурации элемента SqlDataSource выберите команду Configure Data Source. В окне мастера конфигурации раскройте узел Connection string и рассмотрите формат строки соединения с базой Northwind. Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NorthWind.mdf; Integrated Security=True;User Instance=True Обратите внимание на виртуальную папку |DataDirectory|, в которой размещен файл с базой данных. Она ссылается на специальную папку App_Data. Такой способ адресации упрощает размещение проекта (deployment) в рамках реального сервера IIS. Перейдите на следующую страницу мастера, нажав кнопку Next (см. рис. 1.11.), В списке Columns снимите флажок *, который выбирает все колонки таблицы Orders, и выберите флажки, соответствующие лишь некоторым колонкам таблицы заказов: OrderID, CustomerID, OrderDate, Freight и ShipAddress (идентификатор заказа, идентификатор клиента, дата заказа, стоимость перевозки, адрес доставки). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.11. Диалог конфигурации источника данных SqlDataSource Нажмите кнопку WHERE..., которая позволяет установить фильтр отбора данных. В диалоге Add WHERE Clause произведите установки, которые позволят выбирать не все заказы всех клиентов, как было по умолчанию, а только заказы того клиента, идентификатор которого (CustimerID) передан в параметрах адресной строки браузера (QueryString). На рис. 1.12 показаны необходимые для этого установки. Рис. 1.12. Диалог настройки фильтра отбора данных Нажмите кнопку Add, в окне WHERE clause просмотрите результат (фильтр, определяемый SQLоператором WHERE) и нажмите OK. Перейдите на следующую страницу и убедитесь, что оператор SELECT имеет такой вид. SELECT [OrderID], [CustomerID], [OrderDate], [Freight], [ShipAddress] FROM [Orders] WHERE ([CustomerID] = @CustomerID) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Нажмите кнопку Finish, которая завершает работу мастера. Откроется окно с сообщением о необходимости изменить конфигурацию GridView. Согласитесь с предложением отказаться от выбранного ранее (полного) множества колонок элемента GridView и создать новое, определенное нами, множество. После этих манипуляций страница TestDB должна отображать заказы клиента, который указан в параметре адресной строки браузера. Для того, чтобы проверить ее в работе запустите проект и добавьте в конец адресной строки параметр: ?CustomerID=ALFKI. Вы должны увидеть выборку из таблицы заказов, соответствующая клиенту с идентификатором ALFKI. Синтаксис QueryString мы рассмотрим позже, но его и так достаточно просто вычислить. Измените значение параметра CustomerID. Вместо ALFKI введите ANTON (или BERGS) и убедитесь, что строки таблицы фильтруются должным образом (по полю CustomerID — внешнему ключу таблицы Orders). Другие допустимые значения для идентификаторов клиентов вы можете найти в таблице Customers, в которой поле CustomerID является первичным ключом. Для этого отыщите саму таблицу в окне студии Server Explorer и дайте команду Show Table Data. Заметим, что двойной щелчок по таблице в окне Server Explorer демонстрирует не данные таблицы, а ее схему (список колонок и типов данных), как показано на рис.1.14. Рис. 1.14. Схема таблицы клиентов в окне студии Server Explorer Работа со связанными таблицами Если вы рассмотрите структуру таблиц базы данных Northwind (это можно сделать в окне Server Explorer), то увидите, что таблица заказов имеет подчиненную таблицу с именем Order Details. Эти таблицы связаны между собой связью типа PK-FK (Primary Key- Foreign Key). Поле OrderID в таблице Orders выполняет роль первичного ключа, а в подчиненной таблице Order Details — вторичного (связанного). Для работы со связанными таблицами в ASP.NET имеется специальный элемент управления asp:DetailsView. Он позволяет в табличном виде отобразить данные подчиненной таблицы базы данных. Этот элемент одновременно отображает лишь одну из множества строк дочерней таблицы, связанных с главной по полю foreign key. Множество связанных строк зависит от выделенной строки GridView, отображающего главную (master) таблицу. В нашем случае это множество состоит из одной строки — деталей текущего заказа. Текущий заказ (выделенная, или выбранная в данный момент) строка главной таблицы определяется пользователем. Очевидно, для успешной работы механизма DataBinding, надо манипулировать выделенной строкой главной таблицы (свойство SelectedValue). Для этого можно либо установить в true свойство AutoGenerateSelectButton отображения этой таблицы (GridView), либо добавить в множество его колонок (Columns) специальное поле CommandField. Покажем, как легко осуществить этот план с помощью мастера студии. Нам надо ввести в состав Webстраницы элемент DetailsView. Он имеет структуру, сходную с обычным GridView. При этом придется Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 добавить еще один источник данных (SqlDataSource). Он будет снабжать данными детализированный список (DetailsView). В режиме дизайна страницы добавьте в конец Web-формы элемент DetailsView. В окне свойств присвойте ему имя (ID)= dvDetails. С помощью умной кнопки (>) вызовите диалог DetailsView Tasks и откройте выпадающий список Choose Data Source. В списке доступных источников данных выберите <New Data Source...>. В окне мастера вставок выберите тип источника данных Database, укажите его имя dsDetails и нажмите кнопку OK. На следующей странице мастера выберите строку соединения с базой. Она должна быть в выпадающем списке и нажмите кнопку Next. Следущая страница мастера определяет запрос к базе данных, а точнее, конфигурацию оператора Select. В списке Name выберите таблицу Order Details. В списке Columns задайте множество колонок так, чтобы текст запроса имел вид: SELECT [OrderID], [UnitPrice], [Quantity], [Discount] FROM [Order Details] Нажмите кнопку WHERE, которая открывает диалог, формирующий фильтр отбора строк. В списке Column выберите OrderID. В списке Source (источник, из которого поступит параметр оператора WHERE) выберите тип Control. В списке Parameter properties выберите grid. Это означает, что параметр фильтра (OrderID) будет получен из текущей строки элемента GridView, отображающего заказы клиента. Нажмите кнопку Add, а затем OK и Next. На последней странице мастера просмотрите результат всего диалога — оператор SELECT. SELECT [OrderID], [UnitPrice], [Quantity], [Discount] FROM [Order Details] WHERE ([OrderID] = @OrderID) Нажмите кнопку Finish. Для того, чтобы включить механизм привязки к данным (DataBinding), который синхронизирует элементы GridView и DetailsView, надо ввести в состав колонок GridView командное поле Select. Это делается в диалоге Fields (см. рис. 1.15). Рис. 1.15. Диалог управления механизмом привязки к данным Нажмите кнопку конфигурации (>) элемента GridView и выберите команду Edit Columns. В дереве доступных типов полей (Available fields) раскройте узел CommandField, выберите поле Select и нажмите кнопки Add, а затем OK. Нажмите кнопку конфигурации (>) элемента DetailsView, выберите команду AutoFormat. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 В диалоге форматирования элемента DetailsView выберите одну из предлагаемых схем. Осталась установить еще одну деталь, без которой механизм синхронизации данных (DataBinding) не будет работать. Выделите элемент GridView и в окне его свойств задайте свойство DataKeyNames. Оно должно стать равным OrderID. Запустите страницу на выполнение, задайте в командной строке браузера параметр фильтрации клиента (?CustomerID=ALFKI) и убедитесь, что детализированный список синхронизирован с главной таблицей заказов. Он отображает детали заказа, который выбран с помощью командного поля Select (см. рис. 1.16). В отличие от GridView, который отображает связанные поля (BoundFields) в виде колонок таблицы, детализированный список показывает их в виде строк. Учитывая то, что связанные поля соответствуют колонкам реальной таблицы базы данных, можно сказать, что DetailsView транспонирует (переворачивает) ее. Рис. 1.16. Синхронизация данных DetailsView с выбором строки в таблице GridView Усложняем механизм привязки и фильтрации данных В настоящий момент пользователь страницы TestDB.aspx вынужден вводить параметры командной строки. Это является временным неудобством, которое следует устранить. Мастер конфигурации источника данных сделает это автоматически, необходимо лишь правильно настроить параметры уже существующего источника данных dsOrders. Зададимся целью отобразить на странице список всех клиентов в виде выпадающего списка. При выборе определенной строки этого списка должен автоматически произойти отсыл данных на сервер. Сервер должен обработать это событие и сгенерировать новый облик страницы, в котором список заказов отфильтрован в соответствии с выбранным клиентом. Алгоритм отображения деталей заказа оставим без изменения. Работая с макетом страницы в режиме дизайна, вы, конечно, обратили внимание на специальную панель внизу окна, которая упрощает выделение элементов управления страницы и перемещение по ним. Эта панель называется Tag Navigator. Она отображает иерархию вложенности элементов внутри документа. Сам документ удовлетворяет стандарту XHTML, который более строго регламентирует структуру документов HTML и ASPX. Теговый навигатор упрощает слежение за структурой документа и управлять вложенностью элементов. Откройте страницу в режиме дизайна, выделите (с помощью навигатора) панель div и установите фокус ввода в самое начало этой панели. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Введите простой текст: Select a customer:. Заметьте, что для ввода статического, неуправляемого текста нет необходимости создавать элементы вида <span> или <textarea>. Тем более не следует вводить серверные элементы вида <asp:Label>, так как они добавят ненужную функциональность. Достаточно ввести простой текст. Сразу за текстом вставьте выпадающий список (серверный элемент <asp:DropDownList>). Установите для него свойство (ID). Пусть оно будет ddCustomer. При этом автоматически всплывет диалог (см. рис. 1.17), позволяющий настроить список в смысле привязки к данным. Рис. 1.17. Диалог быстрой настройки свойств выпадающего списка В этом диалоге установите флажок Enable AutoPostBack, который обеспечит автоматический отсыл данных страницы на сервер, а затем выберите команду Choose Data Source. В списке доступных источников данных выберите <New Data Source...>. Следующая страница мастера потребует от вас задать тип источника данных и его имя. Выберите Database и dsCustomers. На следующей странице мастера выберите уже знакомую вам строку соединения. Далее произведите установки, показанные на рис. 1.18. Не забудьте установить флажок Return only unique rows. Убедитесь, что сгенерированная строка запроса имеет вид: SELECT DISTINCT [CustomerID] FROM [Customers] и нажмите кнопку Next. Рис. 1.18. Конфигурация источника данных SqlDataSource Следующая страница позволяет проверить соединение и результат обращения к таблице базы данных. Воспользуйтесь этой возможностью, нажав кнопку Test Query. Закончите текущий диалог, а затем и диалог мастера конфигурации. Обратите внимание на то, что диалог конфигурации, изменился и приобрел вид (рис. 1.19). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.19. Модифицированный диалог настройки свойств выпадающего списка Сейчас мы должны изменить способ привязки к данным элемента GridView. Он должен фильтровать свои данные на основе выбора в списке ddCustomer, а не на основе данных адреса (QueryString). Выделите элемент GridView и запустите диалог его конфигурации (с помощью Smart Tag). Выберите команду Configure Data Source. Первую страницу мастера оставьте без изменений, она выбирает все ту же строку соединения с базой NorthWind. На странице конфигурации оператора запроса (Select) надо произвести те же установки, что и ранее (см. рис. 1.11). Мы обращаемся к таблице заказов и выбираем не все, а только желаемые, колонки. Здесь важно то, что мы хотим изменить условие отбора заказов. Для этого нажмите кнопку WHERE. В диалоге Add WHERE Clause произведите установки, показанные на рис. 1.20. Рис. 1.20. Диалог настройки фильтра отбора данных Здесь важно то, что мы изменяем тип источника данных. Вместо QueryString мы выбираем Control (элемент нашей страницы). Важно также правильно выбрать идентификатор этого элемента. Он должен ссылаться на выпадающий список ddCustomer. Нажмите кнопку Add. После этого необходимо удалить предыдущий способ привязки к данным. В окне WHERE Clause выберите строку с указанием на QueryString и нажмите кнопку Remove. Оставьте новую строку с указанием на ddCustomer.SelectedValue. Завершите диалог генерации оператора WHERE и текущую страницу мастера конфигурации. На следующей странице вы можете проверить результат запроса. Нажмите кнопку Test Query. Так как запрос должен иметь параметр (CustomerID), мастер затребует его значение. Введите ALFKI (это идентификатор первого клиента) и убедитесь, что запрос возвращает затребованное множество строк (см. рис. 1.21). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.21. Результат проверки запроса к таблице заказов Завершите мастер и откажитесь от намерения изменить множество колонок GridView. Оно было определено ранее и вполне нас устраивает. Если вы случайно выбрали не тот путь, то в любой момент можете прервать работу мастера и пройти весь диалог заново. Если пропала колонка GridView с командой Select, то вновь откройте диалог Edit Columns и восстановите ее, как было показано ранее. Мастер может изменить значение идентификатора CustomerID (заменить его на CustomerID2). Если вас не устраивает такой произвол, то это легко исправить в режиме редактирования кода разметки. Но важно исправить все вхождения идентификатора. Пользуйтесь командой Find and Replace (Ctrl+H). Запустите страницу на выполнение и убедитесь (см. рис. 1.22), что работать с ней стало проще. Рис. 1.22. Результат двойной фильтрации данных Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Подводя итог, отметим, что с помощью инструментов студии, в рамках технологии Web-приложения может резко сократить время, затрачиваемое на выполнение Перечислим некоторые из них: соединение с источником данных, выбор самих события, возбуждаемые в процессе работы со страницей (их обработка происходит синхронизация элементов управления страницы (DropDownList-GridView-DetailsView). ASP.NET разработчик рутинных операций. данных, реакции на на стороне сервера), Заметьте, также, что при реализации приведенного выше сценария работы с данными мы не написали ни единой строчки кода. Всю работу мог выполнить не программист, а дизайнер. Это, однако не означает, что программисту нечего делать. Он может состредоточить свои усилия на более высоком уровне разработки. Например, он может создать промежуточный слой приложения (его часто называют Middle Tier, или Business Logic Layer — BLL). Такой слой позволит инкапсулировать логику обработки данных с целью повысить надежность и безопасность работы с ними. Часто в архитектуре Web-приложения выделяют слой, непосредственно работающий с данными (DAL, или Data Access Layer). Это делается для того чтобы логика других слоев приложения не зависела от типа источника данных (реальные базы данных, XML-файлы, таблицы Excell, или данные в других форматах). Настоящий параграф демонстрирует возможности инструментов студии, которые генерируют код разметки и код на языке C#. Последний спрятан во временной папке по адресу, начало которого приведено здесь с точностью до диска: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\mysite\... Заметим, что некоторые проблемы удается решить путем удаления этой папки mysite со всем ее содержимым. Это временная папка и она будет снова сгенерирвана студией при запуске проекта. Не перепутайте ее с папкой MySite, которая является папкой вашего проекта (сайта). Реальное проектирование и разработка сайтов потребует знания деталей механизма преобразования документов ASPX в документы HTML, которые отправляются клиенту. В следующих разделах учебника мы снизим уровень сложности рассматриваемого материала и постараемся рассмотреть логику преобразования документов ASPX на стороне сервера, а также некоторые детали работы механизма ASP.NET. 1.4. Специфика функционирования приложений ASP.NET Технология ASP.NET (Active Server Pages) дает возможность динамически формировать документ на стороне сервера и посылать клиенту HTML-код, понятный всем браузерам. Таким образом, ASP.NET — это автомат, работающий на стороне сервера. Целью его работы является генерация HTML-документа, который следует отправить в клиентский браузер. Механизм ASP.NET реализует особую программную модель. Ее главной отличительной чертой является возможность отделить содержимое кода разметки (тегов HTML) от кода его поддержки, реализованного на языке C# (или VB.NET). Предыдущие модели этого не позволяли. Цель этого раздела — показать возможности инструментов студии, автоматизирующих операции добавления новых сущностей. Поэтому мы намеренно не добавляли код в файл поддержки страницы TestDB. Просмотрите этот файл, он содержит пустую заготовку, в которой нет кода. Добавим в него (в метод Page_Load) лишь одну строку кода. Culture = "en-US"; В момент загрузки страницы на сервере (перед тем, как сгененрировать выходной HTML-документ) мы изменяем региональные настройки (Culture = "en-US"). Это делать не обязательно, если обработка данных целиком производится на сервере, но мы хотим показать, как отсортировать данные на стороне клиента. ASP.NET превращает компонент DataGridView в обычную HTML-таблицу <table> и на стороне клиента сортировку приходится делать с помощью кода на языке JScript, который не умеет правильно работать с запятыми, присутствующими в ячейках колонки Freight (см. рис. 1.22). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Так как мы заменили свойство Culture класса Page, то запятые исчезнут, вместо них появятся точки, принятые в большинстве западных культур для разделения целой и дробной частей вещественных чисел (проверьте это). После такой замены код на языке JScript будет правильно сортировать все колонки, содержащие числовые данные. Для сортировки данных, содержащих даты, также придется вводить корректирующий код, но об этом говорить рано. Особенности технологии ASP Прежде всего не следует путать ASP.NET с предшествующей моделью разработки Web-приложений, а именно: ASP. ASP-машина (библиотека asp.dll) генерирует выходной HTML-документ (или объект типа Response) на основе обычного кода разметки и скриптовых вставок на языке VBScript, работающих на стороне сервера. Приведенные примеры будут работать, если поместить их в файл с расширением asp и запустить под управлением IIS. Напомню, что мы работаем под управлением сервера Cassini. Механизм ASP можно рассматривать как виртуальное устройство для генерации HTML-страниц. Оно работает на стороне сервера и создает клиентский HTML-код, опираясь на заданную вами (в файле типа .asp) смесь HTML-кода и скриптовых вставок. Главной заслугой технологии ASP была простота реализации динамически изменяющющегося содержимого. Еще более ранние технологии разработки: CGI (Common Gateway Interface) и ISAPI (Internet Server Application Programming Interface) делали это с более заметными затратами. ASP тесно связана с технологией Microsoft ActiveX Data Objects (ADO), которая использует COM-объекты. Совсем недавно ASP считалась прогрессивной для работы с источниками данных. Код серверного скрипта написан на языке Microsoft Visual Basic Scripting Edition (VBScript). Текст ASP-документа — это причудливая смесь кода, расположенного в блоках <% %>, и тегов HTML. Если в коде ASP-файла отсутствуют скриптовые вставки (блоки вида <% %>), то ядро ASP просто посылает клиенту HTML-текст, существующий в файле. Если они есть, то интерпретатор ASP создает и добавляет в указанные места HTML-документа HTML-текст, суть которого определяется логикой скрипта. В свое время такое решение было прогрессивным, но теперь оно выглядит неуклюжим. Вот пример скрипта на языке VBScript: <html><center> <% For i = 1 To 7 %> <!-- VBScript Code Block --> <font size=<%=i%>>Welcome to ASP</font> <br/> <% Next %> </center></html> Документ ASP обрабатывается на сервере в режиме интерпретатора линейно (сверху вниз). Код скриптовых вставок выполняется во время обработки документа на сервере и забывается после генерации выходного HTML-документа. Вставки содержат код, исполняемый на стороне сервера, и реализует логику, заложенную разработчиком сайта. Например, серверный код может обратиться к базе данных и подставить в текст выходного HTMLдокумента, отправляемого клиенту, результат запроса к БД. Технология ASP не поддерживает объектно-ориентированную модель разработки приложений. В связи с этим затруднено разделение содержимого (content) от его представления (presentation). Такое разделение значительно упрощает выполнение типовых операций, например, одновременную публикацию контента на нескольких веб-узлах (syndication) с сохранением стилей оригинального документа. Кроме того, особый синтаксис интерпретируемого языка VBScript часто отвращает C/C++-программистов от этой технологии. Особенности технологии ASP.NET Заметим, что ядро ASP.NET находится в отдельном модуле (в Windows 2003 Server — это w3wp.exe). Перечислим особенности технологии ASP.NET. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Весь код ASP.NET-приложения, в отличие от ASP, компилируется, что позволяет следить за типами, обнаруживать ошибки, оптимизировать и ускорить процесс генерации HTML-текста. Код поддержки страницы может быть написан на языке C#, VB.NET или JScript.Net (VBScript более не поддерживается). Язык C# — строго объектно-ориентированный, поэтому разделение содержимого документа и его представления не вызывает затруднений. Результат компиляции кэшируется для увеличения производительности сервера. Разработчик приложения ASP.NET может пользоваться скриптовыми вставками в стиле ASP, но может от них отказаться. В ASP.NET работают механизмы, позволяющие помнить состояния элементов управления и эффективно управлять содержимым документа на стороне сервера. ASP.NET имеет несколько механизмов запоминания состояния текущей сессии или всего приложения, как на стороне клиента, так и на стороне сервера. Взаимодействие Client-Server в рамках ASP.NET Взаимодействие Client-Server в рамках ASP.NET имеет специфику, которую важно понимать и учитывать при принятии решений относительно структуры приложения. Ядро ASP.NET (ASP.NET page framework или ASP.NET engine) — это программа, которая постоянно запущена на Web-сервере и предназначена для динамической генерации Web-страниц и управления ими. Она использует библиотеку aspnet_isapi.dll и производит HTML-текст, который передается клиентским браузерам и воспроизводится ими. Динамическими принято называть Web-страницы, которые перед отправкой клиенту проходят цикл обработки на сервере. Когда вы в Web-браузере задаете адрес документа (расположенного на сервере), запускается цепь довольно сложных преобразований, которые объясняют задержку в отображении результата при первом обращении. Кратко их можно описать так. Сервер определяет тип браузера, генерирует и посылает в браузер код страницы, соответствующий этому типу. Более подробно пояснить эти преобразования можно с помощью рис. 1.23. Client Browser R e q u e s t Измененные данные ASP.NET процесс Server Документ изменился ? Да Нет Compile Save Execute R в Рис. 1.23. Схема взаимодействия Client-Server в рамках ASP.NET в Request — запрос клиентаe на обслуживание. Response — ответ или реакция сервера. Библиотека классов s .NET Framework имеет достаточно много классов, поддерживающих работу рассматриваемых нами p собой) элементов системы. (взаимодействующих между o Каждый объект класса Page имеет свойства Request и Response. Request является ссылкой на объект класса n HttpRequest, а свойство Response — ссылкой на объект класса HttpResponse. s Получив запрос, например:e http://localhost:1037/MySite/DBPages/TestDB.aspx, сервер (то есть, IIS или сервер Cassini) преобразует виртуальный http-адрес в реальный адрес своей файловой системы, находит файл TestDB.aspx и начинает обрабатывать его с целью получить код страницы, которую ждет клиент. При первом визите (и при каждом изменении aspx-файла) код компилируется (а не интерпретируется, как было ранее в Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 технологии ASP) и помещается в DLL. Сначала это MSIL-файл, а затем и файл с кодом аппаратной платформы (native code). Они хранятся во временной папке по адресу, начало которого приведено здесь с точностью до диска: C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\mysite\... Продолжением адреса (файлового пути) являются папки, (цифровые) имена которых генерируется системой, поэтому они не приведены здесь. Зайдите внутрь этих папок и вы увидите временные файлы, которые автоматически сгенерированы ядром ASP.NET. Их имена, расширения (XML и DLL), а также содержимое (рассмотрите его) дают лишь отдаленный намек на тот замысловатый механизм, который скрыт под вывеской ASP.NET. Документация MSDN почти не освещает эту тему, так как разработчик, по мнению Microsoft, не должен вмешиваться в этот, секретный механизм. Приведем еще одну схему (см. рис. 1.24), которая приоткрывает завесу над процессом обработки запроса клиента и выработки ответа сервера. Parser — проверяет, интерпретирует содержимое (contents) aspx-страницы и передает его компилятору. Фактически парсер генерирует код класса, производного от класса Page, дополняя его статическим текстом и элементами управления из aspx-файла. Compiler — компилирует содержимое страницы в промежуточный код MSIL. Каждый компьютер с установленной .NET Framework имеет общий кодовывый кеш (assembly cache), который содержит native-версии кода страниц, которые прошли предварительную компиляцию. Memory — временно хранит некоторые, дорогие объекты для того, чтобы сэкономить ресурсы на их повторное конструирование. Output cache — кеш готовой страницы, включая встроенные объекты. Если клиент вновь запрашивает страницу, то она берется из этого кеша. Фаза преобразования при этом пропускается. Request Client Respons e O u t p u t Parser Server Compiler Assembly cash c a s h Memory Рис. 1.24. Кэширование данных на стороне сервера Что делает браузер С точки зрения браузера Web-страница — это длинная строка символов. Последовательно обрабатывая ее, браузер либо воспроизводит в своем окне части этой строки, либо интерпретирует служебные слова (например, <table> или <script>). Если злоумышленник умудрился вставить вредоносные служебные слова в текст страницы, то браузер не сможет отличить их от законных и будет интерпретировать, как часть страницы. Например, если на Web-странице присутствует окно для ввода текста, комментирующго содержимое видеоили аудио-клипа, то пользователь может ввести в него блок <script>. После отправки документа на сервер комментарии пользователей иногда сохраняются в базе данных. Далее, предположим, что другой пользователь хочет видеть существующие комментарии. При конструировании его страницы комментарий читается из базы и вставляется в страницу. При воспроизведении страницы браузер второго клиента встречает блок <script> и интерпретирует его, выполняя код злоумышленника. Внедренный скрипт может быть тривиальным, например рекламой, но он Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 может преследовать и другие цели: находить и отсылать пароль пользователя, его код, документы, cookies (в которых запоминается пароль), и даже запускать код от имени пользователя. Рабочий процесс ASP выполняет код страницы, пользуясь теми же высокими административными правами, что и сам IIS. Для того, чтобы снизить опасность злоумышленного использования этих прав, при установке .NET Framework система автоматически создает фиктивного пользователя ASPNET с криптографически сильным (cryptographically strong) паролем и присваивает ему права группы локальных пользователей (то есть более низкие, чем права членов группы local System). Код DLL генерирует клиентскую страницу (или несколько страниц), учитывая при этом тип клиентского браузера и значения атрибутов формы (которые, возможно, были изменены клиентом). Процесс генерации кода обозначают термином page rendering (отображение страницы). Результат этой процедуры посылается назад (posted back) клиенту. Элементы управления на форме преобразуются в HTML-код (markup) и код скрипта (script). Если клиент использует один из Web-браузеров, то это HTML и JavaScript, а если клиент пользуется мобильным устройством, то это WML и WMLScript. Вы можете увидеть markup-code, который возвратил сервер. Для этого в браузере дайте команду View→Source. Именно этот код работает в браузере и отображает страницу. Последовательность изучения В этом курсе мы рассмотрим, как создавать Web-приложения с помощью ASP.NET. Мы начнем с проектов, которые намеренно не используют инструменты студии. Шаблоны проектов студии для создания Web Form на основе технологии ASP.NET рассчитаны на так называемую двухфайловую модель разработки: ASPX-файл описывает облик страницы, а связанный с ним CS-файл (code-behind) программную логику, или поведение страницы. Такие проекты позволяют автоматизировать типовые задачи, но они создают множество фрагментов с кодом, который трудно воспринимается начинающими программистами. На начальном этапе важно не упустить детали происходящих событий, а именно: преобразований кода и данных. Поэтому целесообразно начать с однофайловой модели разработки приложений. В рамках этой модели визуальные элементы страницы (UI elements) и скрипт, работающий на стороне сервера, расположены в одном файле типа aspx. Однофайловая модель упрощает размещение приложения на сервере (deployment). После этого мы вновь вернемся к рассмотрению приемов быстрого проектирования (RAD), когда включены инструменты студии, ускоряющие процесс разработки Web-приложений, в том числе мы будем использовать двухфайловую модель разработки Web-форм. 1.5. Используем inline-скрипт Откройте студию и выполните следующие действия. Откройте Web Site с именем MySite. Для этого дайте команду File→Open→Web Site и в окне диалога Open Web Site найдите папку с именем MySite. Добавьте к сайту новую папку с именем Intro (команда New Folder в контекстном меню проекта). Вставьте в эту папку новый ASPX-документ (Add New Item→Web Form). Отключите флажок Place code in separate file. Назовите его: Intro00.aspx. Вы видите заготовку aspx-файла, который на сей раз не поддержан файлом с расширением aspx.cs. Весь код должен располагаться внутри файла ASPX. Любой HTML-файл можно (простым переименованием) превратить в файл типа asp или aspx. Следующий текст можно поместить в файл любого типа: .htm, .asp, или .aspx. <html> Name: <input type="text" /></html> Для проверки поместите этот текст в файл Intro00.aspx и и запустите проект (Ctrl+F5). Если клиент вызовет такой документ (будь то .htm, .asp или .aspx-файл), то в любом случае он получит от сервера текст, который Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 вы видите (проверьте, дав в браузере команду View→Source). Спрашивается, какая разница между технологиями? Если текст расположен в файле .htm, то он просто отправится к клиенту. Если этот же текст расположен в файле .asp, то перед отправкой с ним поработает интерпретатор ASP. Он ничего не добавит к документу, так как в нем отсутствует скрипт. Если этот же текст расположен в файле .aspx, то с ним работает ядро ASP.NET. Сложный механизм ASP.NET предварительно компилирует dll, которая затем генерирует HTML-текст (который вы видите) и посылает его клиенту. Вывод. Технологии ASP или ASP.NET будут действительно работать (будут полезны), если кроме HTMLтекста в файле .asp или .aspx будет серверный скрипт. Повторим, что в ASP.NET код скрипта можно и принято выносить в отдельный файл, называемый code-behind (код поддержки Web-страницы). Мы будем писать его на языке C#. Если код скрипта вынесен в отдельный файл (типа .aspx.cs), то принято говорить о двухфайловой модели разработки приложения ASP.NET. Если код скрипта расположен прямо в файле .aspx, то говорят об однофайловой модели. Такой подход характерен для технологии ASP, но он поддерживается и технологией ASP.NET. Опыт с документом ASP Внимание. Здесь мы обсуждаем технологию ASP (не ASP.NET). Файлы с расширенем asp не будут работать в рамках сервера Cassini, но их можно (после настройки) запускать под управлением IIS. Вы можете и не добиться результата, если возникнут вечные проблемы с нехваткой прав на IIS. Мы не будем рассматривать здесь способы выхода из таких ситуаций (они имеют специфику), поэтому лишь прочтите и запомните суть преобразований, производимых ядром ASP. Далее выполняйте только те примеры, которые основаны на технологии ASP.NET (их подавляющее большинство). Напомню, что документы ASPX можно запускать как под управлением IIS, так и под управлением сервера Cassini. Если IIS установлен и настроен, то создайте папку Intro в виртуальном каталоге IIS и работайте в ней. Есле нет, то ограничтесь чтением. Этот пример будет работать только под управлением IIS. Для того, чтобы увидеть и почувствовать различие в обработке сервером обычных HTML-документов и приложений ASP (которые могут быть представлены одним файлом типа asp), возвратитесь в студию для того, чтобы с ее помощью создать такой файл. Добавьте в папку Intro (в виртуальном каталоге) новый документ типа ASP, например Intro00.asp. Так как технология ASP устарела, то диалог Add New Item не позволяет вставить такой документ, но вы можете создать новый файл типа htm, а затем переименовать его в Intro00.asp. Введите код документа, как показано ниже: <html><h3>Now is <%=Now%></h3></html> Приведенный текст, по сути тоже является документом HTML (он ограничен тегами <html> и </html>, но в нем есть серверный скрипт (блок, ограниченный <% и %>), поэтому он достоин статуса asp-документа. Нарушения в синтаксисе HTML (отсутствие блоков <head>, <body> и других) современные версии Internet Explorer прощают. Повторим, любой HTML-файл можно (простым переименованием) превратить в файл типа asp или aspx. Для запуска файлов типа asp или aspx с помощью IIS необходимо разместить их на сервере. Наиболее просто поместить их в папке C:\Inetpub\wwwroot (если IIS установлен, то такая папка уже имеется) и задать адрес файла в поле адреса Web-браузера, например: http://localhost/Intro00.asp. Если вы работаете в виртуальном каталоге MyWeb, то адрес должен учитывать адрес каталога сервера IIS: http://localhost/MyWeb/Intro/Intro00.asp. Откройте Internet Explorer и в поле адреса введите указанный адрес документа. После некоторой задержки, вызванной цепочкой преобразований, производимых сервером, вы должны увидеть Web-страницу. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.25. Результат обработки ASP-страницы на сервере IIS Языком скрипта, принятым по умолчанию, является VBScript. Где VB — там волшебство, поэтому минимальный код скрипта (<%=Now%>) работает и выводит текущую дату. Код VBScript'а внутри блока <% %> пользуется услугами COM-объекта (вот, где волшебство). Он-то и возвращает строку с текущей датой и временем. Специфика скрипта уже не позволяет рассматривать этот документ, как документ HTML. Файл будет работать, имея расширение только asp, но не htm. Если вы получили стандартное сообщение об отказе работать с документом ASP, то надо настроить сервер IIS. Следующий рецепт работает в системе Windows Server 2003 (в XP надо действовать как-то по-другому). Запустите IIS manager, нажав LWin+R и введя в окно Open команду inetmgr. Выделите узел Web Service Extensions, в списке правого окна найдите строку с именем Active Server Pages и с помощью команды Allow измените статус этого расширения (команду можно найти в контекстном меню). Расширение должно быть разрешенным (Allowed), а не запрещенным (Prohibited). Убедитесь также, что статус расширения ASP.NET (с учетом текущей версии) также установлен в значение Allowed. Разделение труда разработчиков Внимание. Здесь мы обсуждаем ASP.NET (не ASP). Технология разработки пользовательского интерфейса состоит из двух частей: создание визуальных компонентов и разработка программной логики. Для обозначения визуальных элементов Web-страницы используется термин Web Forms page (страница). Для управления логикой поведения элементов используется код. Он может иметь вид inline-скрипта (code render block) или отдельного кода поддержки страницы (code-behind). Сode-behind — это файл на одном из языков программирования, который компилируется ядром ASP.NET в DLL и исполняется на стороне сервера. Одним из способов добавки скрипта, выполняемого на стороне сервера, может быть вставка в content документа inline-скрипта (блока <% %>). Наличие inline-скрипта говорит серверу, что перед тем, как сгенерировать текст HTML и послать его в браузер, надо обработать код внутри блоков и заменить его на обычный код разметки (markup code). Код inline-скрипта, по сути представляет собой сокращенную запись выражения Response.Write. Если мы захотим воспользоваться языком C#, то для достижения результата, который был получен в предыдущем примере, придется приложить некоторые усилия. Добавьте в проект файл Intro00.aspх, и вставьте в него такой код. <html><% Response.Write ("<h3>Now is " + DateTime.Now + "</h3>"); %></html> Запустив страницу на выполнение, вы увидите, что выходной документ имеет тот же облик, что и ранее. Однако, усилий затрачено немного больше (исчезло чудо). В качестве компенсации за дополнительные усилия мы получили код, имеющий строгую логику, в которой нет волшебства. Структура .NET DateTime имеет статическое свойство Now (которое и возвращает дату и время), объекту Response приказано выполнить статический метод Write (никакого волшебства). При анализе результатов полезно выработать привычку в браузере давать команду View→Source и просматривать HTML-код, который был сгенерирован сервером (машиной ASP или ASP.NET) и послан Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 клиенту. Просмотр результата позволяет проверить, действительно-ли, как заявляет Microsoft, технология ASP.NET формирует HTML-документ с учетом типа клиентского браузера. Свойства браузера Изменим код страницы Intro00.aspx так, чтобы она кроме времени выводила тип клиентского браузера. Эта информация спрятана в запросе клиента и выудить ее можно с помощью свойства Request класса Page. Код страницы настолько прозрачен, что не требует дополнительного комментария. Класс Page мы рассмотрим чуть позже. <%@ Page Language="C#"%> <html> <% Response.Write ("<h4>Now is " + DateTime.Now); HttpBrowserCapabilities cap = Request.Browser; Response.Write ("<br/><br/>Your web browser is " + cap.Browser + ' ' + cap.MajorVersion + '.' + cap.MinorVersion + "</h4>");%> </html> Рассматриваемая страница генерирует код разметки, который зависит от типа клиентского браузера и календарного времени. Дайте команду View→Source и просмотрите содержимое: <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Browser</title></head> <body> <h4>Now is 24.05.2008 21:53:07<br><br>Your web browser is IE 7.0</h4> </body></html> Класс Page Чтобы сохранить однофайловую модель разработки, сделайте копию предыдущего файла, переименуйте его в Intro01.aspx и подключите к проекту (все это проще выполнить с помощью операции Ctrl+Drag+Drop в окне Solution Explorer). Замените код на тот, что приведен ниже. <%@ Page Language="C#" %> <%@ Import Namespace="System.Threading" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Test Server Script</title> <script runat="server" type="text/C#"> string time; protected void Page_Load(object sender, EventArgs e) { lblTime.Text = DateTime.Now.ToString(); Thread.Sleep(1000); time = lblTime.Text; } </script></head> <body><div> <h2>Test Server Script</h2> <p>The time is: <asp:Label runat="server" id="lblTime" /></p> <p>The time is: <% =DateTime.Now.ToString() %></p> <p>The time is: <% =time %></p> <p>The time is: <% Response.Write(DateTime.Now.ToString()); %></p> </div></body></html> Здесь, как и в предыдущем примере, присутствует скрипт, работающий на стороне сервера. Он расположен как в блоках <% %> (inline-скрипт), так и специальном блоке <script>. Вы должны осознать, что скрипт может иметь несколько почти эквивалентных форм. Пример показывает, что один и тот же результат можно получить различными способами и различия заключаются лишь в способе обработки на сервере. Во всех случаях серверный скрипт работает с переменными и свойствами класса Page. Мы просим сервер указать текущую дату и время, но используем при этом 4 разных приема: Изменяем текст метки lblTime в момент обработки события Load класса Page, Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Используем inline-скрипт, который отображает свойство Now класса Page, Используем inline-скрипт, который отображает значение переменной time класса Page. Значение переменная time получила в момент обработки события Load, Вызываем метод Write класса HttpResponse. Метод генерирует нужный нам HTML-content. Объект класса HttpResponse добывается с помощью свойства Response класса Page. Как видите, везде участвует класс Page, который на стороне сервера поддерживает нашу страницу (или Web-форму). Класс Page автоматически создается ядром ASP.NET во время компиляции каждого файла типа aspx. В рамках однофайловой модели мы не видим ни самого класса Page, ни объявления объекта этого класса. Все это делается, за кулисами (рабочим процессом ASP.NET). Помните, что, используя однофайловую модель, вы всегда можете работать с объектом класса, производного от Page. Он уже создан (неявно) и код, заключенный в блок <script runat="server" type="text/C#"></script>, — это код класса Page. Рис. 1.26. Отображение переменных класса Page и inline-скрипта Когда мы перейдем к двухфайловой модели, то в явном виде увидим объявление и код класса, производного от Page. Он будет в отдельном файле с расширением .aspx.cs, который называется (Code behind the page). Этот файл (точнее, класс) должен поддерживать Web-страницу. Сейчас у нас нет такого файла, но класс, производный от Page, существует. Мы работаем с его переменными и методами в блоке <script> с атрибутом runat="server". Атрибут сообщает компилятору, что скрипт работает на сервере. Клиентский скрипт вовсе не имеет этого атрибута. Любая страница ASP.NET может иметь как клиентский, так и серверный скрипт. Что делает директива @Page? Она, во-первых, напоминает нам, что страница неявно поддержана классом Page, во-вторых, позволяет задать атрибуты, специфические для данной страницы. Атрибуты используются на этапе разбора страницы и ее компиляции (см. в MSDN описание ASP.NET Framework parser). Атрибут Language="C#" указывает язык программирования. Вооружившись классами, мы можем развивать логику скрипта по нашему усмотрению, пользуясь их методами и свойствами. Объекты Request, Response, Server, Application, Session встроены в ASP.NET, поэтому нет нужды вставлять директивы видимости пространства имен, в котором они определены. Обратите внимание на то, что в нашем примере необходимо иметь такую директиву. <%@ Import Namespace="System.Threading" %> На самом деле объекты Request, и другие являются свойствами класса System.Web.UI.Page. Как было указано ранее, свойства дают доступ к объектам. Например, Request является ссылкой на объект класса HttpRequest, а Response — ссылкой на объект класса HttpResponse. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Стиль спагетти MSDN довольно часто (особенно, при описании технологии ASP) приводит примеры, где скрипт и HTML перемешаны. Следующий документ (Intro02.aspx) показывает, как использовать обычный цикл языка C# для того, чтобы управлять значением атрибута size тега <font> (уже устаревшего в стандарте XHTML). <%@ Page Language="C#" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Welcome</title></head> <body><center> <% for (int i=1; i <8; i++) { %> <font size="<%=i%>">Welcome to ASP.NET</font> <br/> <% }%> </center></body></html> Рис. 1.27. Результат работы inline-скрипта в стиле ASP Иерархия классов Следующий пример (Intro03.aspx) выводит иерархию классов, в которой расположен класс Page. <%@ Page Language="C#" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Class Family tree</title></head> <body style="font-family:Courier New; font-size:smaller;"> <% Response.Write("Class Family tree: <br/> <br/>"); Type type = GetType(); do Response.Write(type.ToString() + "<br/> "); while ((type = type.BaseType) != null); %> </body></html> Пример выводит такой текст: Class Family Tree: ASP.intro_intro03_aspx System.Web.UI.Page System.Web.UI.TemplateControl System.Web.UI.Control System.Object Обратите внимание на то, что компилятор сгенерировал класс intro_intro03_aspx, производный от класса Page и поместил его в пространство имен ASP. Код с объявлением этих сущностей можно отыскать в папке с временными файлами ASP.NET. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Свойства объекта HttpContext Рассмотрим пример (Intro04.aspx), иллюстрирующий некоторые свойства объекта HttpContext. Доступ к нему дает свойство Context класса Page. Класс HttpContext инкапсулирует информацию о текущем состоянии страницы. <%@ Page Language="C#" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Context Properties</title> <style type="text/css"> body { font-family:Tahoma; font-size:x-small;} td { text-align:left; background-color:#ffffdd; } table { border:5px solid; border-color:Highlight; } </style> </head> <body> <table ></table> <% Response.Write("<table cellpadding='5px'>"); string s = Context.User.Identity.Name; Response.Write("<tr><td>Context.User.Identity.Name</td><td>" + s + "</tr>"); s = Context.CurrentHandler.ToString(); Response.Write("<tr><td>CurrentHandler</td><td>" + s + "</tr>"); s = Context.Session.SessionID.ToString(); Response.Write("<tr><td>SessionID</td><td>" + s + "</tr>"); s = Context.Timestamp.ToShortDateString(); Response.Write("<tr><td>Timestamp</td><td>" + s + "</tr>"); Response.Write("</table>"); %> </body></html> Рис. 1.29. Некоторые свойства объекта HttpContext Идентификатор сессии SessionID позволяет уникальным образом идентифицировать текущую сессию. Сессией называется сеанс связи с конкретным клиентским компьютером, который обслуживается отдельным экземпляром рабочего процесса ASP.NET. Кроме того, SessionID поддерживает коллекцию Session, которая позволяет хранить произвольную информацию о текущем состоянии приложения. Мы рассмотрим способы работы с этой коллекцией позже. Смысл других свойств объекта HttpContext следует из их названий. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 1.6. Используем форму Тег <form> играет решающую роль в технологии ASP.NET. Именно он включает механизм запоминания состояний серверных элементов и отложенной обработки событий, генерируемых этими элементами. Сначала мы рассмотрим форму, на которой нет серверных элементов управления (на ней будут лишь обычные HTML-элементы.) и убедимся, что такая форма не помнит состояний своих элементов. Затем мы добавим серверный скрипт, который устранит этот недостаток. В конце концов мы создадим аналогичную форму, но с использованием серверных элементов и убедимся, что она автоматически запоминает состояния дочерних элементов и необходимость разрабатывать код для обновления полей ввода данных отпадает. Следующий документ Intro05.aspx состоит из двух частей: заголовка (ограниченного тегами <head> и </head>) и тела (между тегами <body> и </body>). Внутрь тела (между <body> и </body>) помещены теги, создающие стандартные HTML-controls (элементы управления): окно ввода (<input>) и окно списка (<select>). Теги: <center>, <h3> (header 3-го уровня) являются форматирующими и их смысл очевиден. <%@ Page Language="C#"%> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Using Form</title></head> <body><center> <form action="Intro05.aspx" method="get"> <h3>Name:&nbsp;<input name="name" type="text" /><br /><br />Language:&nbsp; <select size="4" name="lang"> <option>Assembler</option><option>C++</option> <option selected="selected">C#</option><option>PL/I</option> </select></h3> <input type="submit" value="OK"/> </form></center> </body></html> Страница, как видите, не имеет скрипта, но она содержит форму с вложенными в нее элементами управления. Наличие тега <form> позволяет указать способ передачи состояний элементов, которые являются частью формы. Атрибут method тега <form> позволяет задать (а в другом контексте и узнать), как данные из формы будут отосланы на сервер. В нашем случае для этого будет использован метод get (см. атрибут method="get"). Существует еще один метод общения с сервером — post. Суть методов поясним позже, а сейчас запустите новый документ и просмотрите результат. Убедитесь, что при нажатии на кнопку OK данные, введенные пользователем теряются. Сервер заново загружает страницу в ее начальном состоянии, он не запоминает значения полей ввода (результат взаимодействия пользователя с элементами формы). Это можно исправить, добавив серверный скрипт, который извлекает данные формы и устанавливает их в новое состояние перед тем, как отправить результирующий HTML-документ клиенту. Методы Get и Post Взаимодействуя со страницей, пользователь заполняет форму данными, которые она хранит в виде пар name=value. В примере Intro05.aspx присутствует важный элемент, который часто имеется на форме. Это кнопка, имеющая тип submit. Ее нажатие завершает диалог и отсылает данные формы на сервер. Значение submit атрибута type говорит браузеру, что он должен создать кнопку, в обработке нажатия которой следует собрать данные формы и передать (submit) их для обработки на сервер. Эти данные упаковываются в сообщение (Request message). Если атрибут method тега <form> установлен в значение get, то данные посылаются вместе с URL и их можно увидеть в адресной строке браузера. В ASP.NET данные хранятся в коллекции, доступ к которой дает свойство Request.QueryString. Если значение атрибута равно post, то данные упаковываются и передаются в теле документа. Их можно добыть из коллекции, которую возвращает свойство Request.Form. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Еще раз уясните способ передачи данных, выбранный для документа, затем запустите его на выполнение и убедитесь, что нажатие кнопки OK эквивалентно команде Refresh (или F5), то есть, данные, введенные клиентом, теряются. Рассмотрим более пристально синтаксис тега <form>. Идентификатор формы Запрашиваемая страница Данные посылаются вместе с URL <form id="myForm" action="Intro05.aspx" method="get"> Атрибут action устанавливает адрес (точнее, URL) приложения, которое должно обработать содержимое страницы. Если этот адрес совпадает с адресом самого документа, то говорят, что страница посылает данные сама себе. Такое событие обозначают термином Client Post Back (возврат данных клиенту). Если атрибут action установить в другое значение, то данные формы будут переданы другой странице. Для того, чтобы отличить первоначальную загрузку документа от повторной посылки (posting back), используют булевское свойство IsPostBack, которое имеется в каждом объекте класса Page. Оно равно true в случае повторной отправки данных и false в случае первоначальной загрузки. Метод отправки определяется значением атрибута method. Значение get сообщает, что данные формы будут пристыкованы (appended) к адресу (action URL) и этот адрес должен быть обработан так же, как и обычная ссылка (anchor). При этом данные посылаются в секции <head> HTTP-запроса. Такой способ передачи имеет ограничение—объем не должен превышать максимальной длины URL (адрес вместе с данными не должен превышать 256 байт). Когда данные обычных HTML-элементов формы отправляются на сервер (в документации на английском языке это действие обозначают — form is submitted), то только пары вида (name, value) попадают в коллекцию Request.QueryString. Если элемент не имеет атрибута вида name="myTextBox", то его данные не доступны на стороне сервера. Важно запомнить, что элементы могут иметь как атрибуты name, так и атрибуты id. Но в коллекцию Request.QueryString попадут только значения тех элементов, которые имеют атрибут name. Значения элементов, имеющих только атрибут id доступны лишь на стороне клиента (с помощью кода JScript). Вы можете применить и другой метод передачи данных, считанных с формы, а именно, post. В этом случае данные будут переданы с помощью механизма HTTP post transaction. Данные посылаются в секции <body> и такой способ теоретически не имеет ограничений на объем. Практическим ограничением являются лишь возможности клиентского компьютера. Просмотр данных Вернемся к предыдущему примеру. Сосредоточьте внимание на поле адреса. В поле Name введите какоенибудь имя (например, Alex) и нажмите кнопку OK. В поле адреса браузера вы увидите: http://localhost/MyWeb/Intro/Intro02.aspx?name=Alex&lang=C%23 Заметьте, что к URL добавлена строка ?name=Alex&lang=C%23. Знак вопроса — это разделитель, за ним следуют пары: name=Alex и lang=C%23. Они хранят результат считывания данных формы. Данные добавлены к URL и вместе с ним отправляются на сервер. Скрипт на стороне сервера может обработать полученные данные, но в данный момент такой скрипт отсутствует. Теперь замените метод get на post и повторите опыт. После нажатия OK, в поле адреса URL вы не увидите данных формы в строке адреса, так как они передаются другим способом (с помощью механизма HTTP post transaction). Добавляем скрипт на стороне сервера Итак, после нажатия кнопки OK, установленные вами состояния всех элементов управления не сохраняются—происходит их сброс. Такое поведение отчасти объясняется значением атрибута action (которое вновь запускает уже сыгранную пластинку), отчасти тем, что мы не используем серверный скрипт Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 (код, позволяющий реагировать на данные формы и изменять состояния элементов). Добавьте в проект новый файл Intro06.aspx. Введите в него код, показанный ниже. <%@ Page Language="C#" %> <script language="C#" runat="server"> DateTime theTime; protected void Page_Load(object o, EventArgs e) { Title = ConfigurationManager.AppSettings["title"] + "Using QueryString"; theTime = DateTime.Now; DataBind(); } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"><title>Intro06</title></head> <body><center> The time is: <%# theTime %><br/> <h3>Query Form</h3> <form action="Intro06.aspx" method="get"> <%--../HTML/SplitQuery.htm --%> <h4>Name: <input type="text" name="name" value='<%=Request.QueryString["name"]%>' /> <br/> Language: <select name="lang" size="4"><% string [] values = { "Assembler", "C++", "C#", "PL/I" }; for (int i=0; i<values.Length; i++) {%> <option <%if (Request.QueryString["lang"] == values[i]) Response.Write(" selected ");%> > <%=values[i]%> </option><% } %> </select> <input type="submit" name="ok" value="OK" /></h4><% if (Request.QueryString["ok"] != null) { %> Hi <%=Request.QueryString["name"]%>, you selected: <%=Request.QueryString["lang"]%><% } %> </form></center></body></html> Здесь используется скрипт, работающий на стороне сервера. Он написан на языке C#. Вы видите два разных стиля серверного скрипта: встроенный, или inline-script (в стиле технологии ASP) и скрипт внутри блока <script></script>. Встроенный, (расположенный внутри блоков <% %>) называют spaghetti style script, так как в документе он перемешан вместе с текстом HTML. Скрипт в блоке <script>, хоть и находится в одном файле с текстом разметки, но он выделен в блок (при использовании двухфайловой модели блок исчезает, а скрипт попадает в отдельный файл). Как вы догадались, выделенный скрипт обрабатывает событие загрузки документа на стороне сервера. В частности, он изменяет титул страницы. Вместо текста Intro06, который жестко задан в коде разметки, он обращается к свойству Title класса Page и подставляет другой текст титула. Title = ConfigurationManager.AppSettings["title"] + "Using QueryString"; Титул заголовка (свойство Title класса Page) не равен null потому, что тег <head> имеет атрибут runat="server". Если его убрать, то титул заголовка станет недоступен на стороне сервера. Настройка AppSettings["title"] в данный момент отсутствует. Откройте файл Web.config, найдите тег <appSettings /> и заменте его следующим фрагментом. <appSettings> <add key="title" value="Learn ASP.NET. " /> </appSettings> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Класс ConfigurationManager позволяет достать определенные настройки из файла Web.config. Некоторые серверные элементы управления умеют самостятельно добывать текстовые строки из файла конфигурации. Например, элементы типа SqlDataSource или AccessDataSource добывают оттуда строки соединения с базой данных. Наш скрипт использует механизм привязки к данным (DataBinding). Переменная theTime класса Page получает свое значение в момент обработки документа на сервере, поэтому она отражает серверное время. Скриптовая вставка <%# theTime %>, которую вы видите в теле документа, синхронизирована с значением переменной theTime. Синхронизацию выполняет статический метод DataBind класса Page. Повторим, что весь скрипт примера работает на сервере. Вызовите документ из браузера. Когда форма появится на экране, введите имя, сделайте выбор в списке и нажмите кнопку OK. Вы увидите страницу, которая показана на рис. 1.30. Рис. 1.30. Восстановление данных с помощью коллекции Request.QueryString Заметьте, что по сравнению с предыдущим документом появилась возможность не только запоминать и воспроизводить значения атрибутов элементов, но и реагировать на их изменения. Однако, структура документа стала значительно более сложной, смешение скрипта и гипертекста отнюдь не повысило читабельность (довольно простого) документа. Корректировать и сопровождать такой код стало более сложно. Именно эти недостатки призвана устранить технология ASP.NET. Различайте специальные декларативные теги, начинающиеся с пары символов <%. Они имеют различный смысл. К настоящему моменту мы использовали несколько таких тегов: <%@ — директива, или указание компилятору, <% — встраиваемый серверный код, <%= — подстановка значения выражения, вычисленного на сервере (синоним Response.Write), <!-- #Include virtual="/include/header.inc" --> — директива Server-Side Include, <%-- — серверный комментарий, <%# — настройки механизма привязки к данным (DataBinding expression), <%$ — вычислитель выражений (Expression evaluator), используется для вычисления строк соединения с базами данных или настроек приложения. Внимательно проанализируйте все изменения в коде документа. Попробуйте ответить на вопрос: Где происходит реакция на изменения данных формы (на стороне клиента или на стороне сервера)? Уберите символы ', обрамляющие вторую скриптовую вставку, затем введите имя, состоящее из двух слов (например, Peter Norton), проверьте работу страницы и дайте оценку. Замените атрибут name любого из элементов на id и вновь проверьте работу страницы. Измените метод передачи данных (вместо get вставьте post) и снова проверьте. Ответы Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Реакция на изменения данных формы происходит на стороне сервера, так как скрипт, заключенный в блок <% %>, всегда работает на стороне сервера. Коллекция QueryString имеет тип NameValueCollection. Такие коллекции хранят пары вида (string, string[]). Обращение к коллекции вида: Request.QueryString["name"] возвращает оба слова "Peter Norton", но если убрать ограничители, то в поле ввода name попадает только одно—Peter. Вторая лексема (Norton) пропадет. Замена атрибута name на id разрушает логику скрипта, так как запрос вида: QueryString["lang"] подразумевает, что "lang" является значением атрибута name, а не id. Изменение способа передачи данных (post вместо get) приведет к исчезновению коллекции QueryString, на использовании которой построен скрипт. 1.7. Анализ параметров строки запроса Семейство классов ASP.NET позволяет довольно просто произвести синтаксический анализ (parsing) параметров адресной строки браузера (вы помните, что она называется QueryString) и использовать их для каких-то целей, например, вывести значения параметров в окно браузера. Добавьте в проект еще один документ Intro07.aspx, выполненный в рамках однофайловой модели и вставьте в него следующий код. <%@ Page Language="C#" AutoEventWireup="true" %> <script language="C#" runat="server"> protected void Page_Load(object sender, EventArgs e) { NameValueCollection data = Request.QueryString; string msg = data.Count == 0 ? "No QueryString Data" : "QueryString has the following data"; Response.Write("<h3>" + msg + "</h3>"); for (int i = 0; i < data.Count; i++) { Response.Write("<br/>" + (i + 1).ToString() + ". " + data.GetKey(i).ToString() + " = " + data[i].ToString()); } } </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head><title>Show QueryString</title></head> <body> <form id="form1" action="Intro06.aspx" method="get"> <div> <input type="submit" value="Submit"/> </div> </form> </body></html> Внесем изменение в тег <form> страницы Intro06, которое заставят ее не совершать postback, а передать данные новой странице Intro07. Для этого надо изменить атрибут action ="Intro07.aspx". Страница Intro07, как видите, уже отправляет клиента обратно, на страницу Intro06. Запустите в браузере страницу (Intro07) и убедитесь, что параметры QueryString отсутствуют, Нажмите кнопку Submit, заполните форму документа Intro06 и нажмите OK. Убедитесь, что управление перешло к странице Intro07 и она отображает значение элементов управления предыдущей страницы (они видны в адресной строке браузера). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.31. Разбор адресной строки с помощью коллекции Request.QueryString При переходе к другой странице данные формы передаются с помощью механизма QueryString. Этот пример наглядно демонстрирует, что QueryString представляет собой динамическую коллекцию пар вида (name, value). NameValueCollection — специализированная (или типизированная) коллекция. Она работает по принципу сортируемой хеш-таблицы SortedList, но в отличие от последней (обобщенной коллекции) хранит не произвольные пары объектов, а только пары типа (string, string[]). Эта коллекция позволяет ассоциировать ключ (первый элемент пары) типа string с массивом текстовых строк. Кроме того, она допускает выбор значений (value) как с помощью ключа, так и с помощью целочисленного индекса. Метод GetKey класса NameValueCollection, как нетрудно догадаться, возвращает ключ i-й пары. Выбор значения (см. код data[i]) производится с помощью индексатора. Статический метод Write класса HttpResponse, добываемого с помощью свойства Response класса Page, позволяет сгенерировать HTML-контент, который возвращается клиенту с помощью кодированного потока (HTTP stream). Для того, чтобы увидеть весь текст кодированного потока, добавьте в метод Page_Load такой код: Request.SaveAs("D:\\request.txt",true);. Откройте созданный файл и убедитесь, что запрос имеет вид. GET /MySite/Intro/Intro07.aspx?name=Alex+Black&lang=PL%2FI&ok=OK HTTP/1.1 Connection: Keep-Alive Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwaveflash, application/x-ms-application, application/x-ms-xbap, application/vnd.msxpsdocument, application/xaml+xml, application/vnd.ms-excel, application/vnd.mspowerpoint, application/msword, */* Accept-Encoding: gzip, deflate Accept-Language: ru Cookie: ASP.NET_SessionId=i4zqoa453myvb1agqyplmeat Host: localhost:1037 Referer: http://localhost:1037/MySite/Intro/Intro06.aspx User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; InfoPath.2) UA-CPU: x86 Пары вида (текст : текст) представляют собой элементы коллекции заголовков (HTTP Headers). Позже мы покажем, как воспользоваться ею для передачи вспомогательной информации. Добавляем клиентский скрипт Далее мы часто будем вставлять клиентский скрипт (JScript). Вы знаете, что настройки браузера позволяют рарешить его выполнение или запретить. Проверьте настройки вашего браузера и разрешите скрипт. Для этого установите флажок: СервисСвойства обозревателяБезопасность Местная интрасетьПараметры безопасностиСценарииАктивные сценарии Разрешить. Покажем, как произвести синтаксический разбор адресной строки (action URL) и выудить из нее пары name=value в случае, когда данные формы передаются другой странице методом get. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Напомним, что в рамках технологии ASP.NET эти пары (на сервере) можно добыть из коллекции QueryString. Это обычная hash-таблица, с ней работать очень просто и мы уже показали используемую при этом технику (см. код Request.QueryString["lang"]). Но теперь страницей-приемником данных будет не документ ASPX, а обычная страница HTML. В этом случае сервер отдыхает, он не работает с файлами HTML, он просто передает данные следующей странице, пристыковав их к строке запроса. Если мы хотим добыть параметры адресной строки, то это надо делать с помощью кода JScript на стороне клиента. Для проверки кода, вам надо изменить атрибут action страницы Intro06.aspx так, чтобы она передала управление документу HTML. Измените атрибут action="../HTML/SplitQuery.htm". В папке HTML создайте документ SplitQuery.htm и введите в него следующий код. <!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><title>Splitting Query String</title> <link rel="Stylesheet" href="../StyleSheet.css" type="text/css" /> <script type="text/jscript"> function Parse() { var parts = document.URL.split("?"); if (parts.length <= 1) { sp.innerHTML = "<h4>This page parses OueryString</h4>" + "There are no (or wrong) OueryString parameters now"; return; } var parameters = parts[1].split("&"); var span = document.getElementById("sp"), msg = "<h3>Query parameters</h3>" + "<table border='1' cellpadding='3' cellspacing='0'>" + "<tr><th><b>Key</b></th><th><b>Value</b></th></tr>"; for (var i = 0; i < parameters.length; i ++) { msg += "<tr>"; var pair = parameters[i].split("="); msg += "<td>" + pair[0] + "</td><td>" + unescape(pair[1]) + "</td></tr>"; } msg += "</table>"; span.innerHTML = msg; } </script> </head> <body onload="Parse()"> <form action="SplitQuery.htm" method="get" id="myForm"> <span id="sp"></span> </form></body></html> В предыдущем случае мы обрабатывали событие загрузки страницы на сервере (метод Page_Load), а теперь обрабатываем событие загрузки страницы в браузере (см. реакцию на событие onload). При этом вызывается функция Parse(). Она сначала разбивает строку запроса на две части, а затем правую часть разбивает на части, содержащие пары данных вида (name=value). Первое разбиение осуществляет такой код JScript: var parts = document.URL.split("?"); Знак вопроса выступает в качестве разделителя, который расположен между адресом URL и параметрами строки запроса. Результатом разбиения будет массив строк parts, содержащий два элемента: ["http://localhost:3227/MySite/HTML/SplitQuery.htm", "name=Alex+Black&lang=PL%2FI"] Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Далее мы разбиваем вторую из этих строк: var parameters = parts[1].split("&"); Разбиение дает массив из трех пар: ["name=Alex+Black", "lang=PL%2FI", "ok=OK"] В цикле прохода по этим парам производится еще одно разбиение: var pair = parameters[i].split("="); Оно делит каждую пару на две составляющие, элементы пары: name и value. Для удобства анализа мы не поленились и поместили результаты в ячейки таблицы и это действие занимает львиную долю кода. Вызов unescape позволяет заменить служебный символ (%2F) на обычный (/). Вообще метод unescape возвращает строку, в которой все символы в шестнадцатеричном формате %xx заменяются эквивалентными им символами ASCII. Например, метод unescape заменит символы C%2B%2B на C++. Заметим, что символы Unicode в формате %uxxxx заменяются символами Unicode в формате xxxx. Сделайте стартовой страницу Intro06.aspx, запустите ее, аккуратно введите данные и нажмите кнопку, которая передаст управление странице SplitQuery.htm. Результат выглядит так. Key Value name Alex+Black lang PL/I ok OK Сопоставьте данные таблицы со строкой запроса, которая видна в адресной панели браузера. 1.8. Передача данных с помощью метода post Вы помните, что кроме метода get существует другой метод отправки данных формы на сервер — post. Данные при этом передаются в теле документа, а управлять ими в приложениях ASP.NET можно с помощью коллекции Form. Эта коллекция также имеет тип NameValueCollection, то есть, хранит пары вида (string, string[]). Следующий документ Intro08.aspx демонстрирует, как передать данные с помощью механизма HTTP post transaction и добиться поведения, сходного с тем, что было при передаче данных методом get. <%@ Page Language="C#" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Using Request.Form Collection</title> <script type="text/jscript"> function TestValid() { var o = document.all.namedItem("name"); // Или myForm.name; if (o.value == "") document.all.msg.innerText = "Please, enter a name"; return o.value != ""; } </script> </head> <body><h3>Query Form</h3> <form action="Intro08.aspx" method="post" name="myForm" onsubmit="return TestValid();"> <h4>Name: <input type="text" name="name" value='<%=Request.Form["name"]%>' /> <br/> Language: <select name="lang" size="4"><% string [] values = { "Assembler", "C++", "C#", "PL/I" }; Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 for (int i=0; i<values.Length; i++) {%> <option <%if (Request.Form["lang"] == values[i]) Response.Write(" selected ");%> > <%=values[i]%> </option><% } %> </select> <input type="submit" name="ok" value="OK" /></h4> <% if (!string.IsNullOrEmpty(Request.Form["name"])) { %> Hi <%=Request.Form["name"]%>, you selected: <%=Request.Form["lang"]%><% } %> <br /> <span id="msg" style="color:Red;" /> </form> </body></html> Рис. 1.32. Отображение данных коллекции Request.Form Здесь мы продвинулись несколько дальше и добавили реакцию на событие onsubmit. Это событие относится к тегу <form>, возбуждается при отправке данных на сервер и обрабатывается на стороне клиента. Вы помните, что документ ASPX может иметь оба типа скрипта (клиентский и серверный). В функции обработки события проверяется, есть-ли текст в поле ввода name. Если он отсутствует, то переход по атрибуту action не осуществляется. function TestValid() { var o = document.all.namedItem("name"); // Или myForm.name; if (o.value == "") document.all.msg.innerText = "Please, enter a name"; return o.value != ""; } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рассмотрим, как работает страница в момент отправки данных на сервер. Найдите атрибут onsubmit. Его значение (булевского типа) является результатом обращения к функции TestValid (см. код return TestValid();). Если функция вернет true, то данные формы отправятся на сервер, если — false, то нет. Как видите, возвращаемое значение равно результату булевского выражения o.value != "" Значение true вырабатывается, если в момент нажатия на кнопку submit поле ввода name не пусто. Не путайте поле name и атрибут name нашей формы. Они имеют разный смысл. Ссылка (o) на поле ввода name добывается с помощью методов document. Более подробную информацию об объектах языка JScript и модели DOM можно получить в Модуле 1. Подумайте, как правильно обработать случай, когда в поле ввода name есть одни пробелы. Найдите и сформулируйте все отличия от предыдущего варианта (method="get"). Форма (тег <form>) имеет атрибут name и не имеет атрибута id. Такой способ идентификации уже устарел. Стандарт требует задавать атрибут id. Если заменить name на id, то ссылку на поле name можно добыть так: document.getElementById("name");. В последних версиях IE getElementById работает как для атрибута name, так и для атрибута id. Просмотрите справку по свойству Form класса HttpRequest. Замените тег <span id="msg" style="color:Red;" /> следующим блоком кода (Code Render Block) и разберите логику его функционирования. <span id="msg" style="color:Red;"> <% string[] keys = Request.Form.AllKeys; if (keys.Length > 0) Response.Write("<br/>Request.Form Collection has the following pairs:<br/>"); for (int i = 0; i < keys.Length; i++) Response.Write((i+1).ToString() + ". " + keys[i] + ": " + Request.Form[keys[i]] + "<br/>"); %> </span> Запустите приложение, просмотрите его вывод и ответьте на вопрос. Почему при первом обращении к документу этот блок кода не выводит информации? Заметьте, что данные формы не отправляются на сервер, то есть, функция submit (сбор и отправка данных) блокируется, если результат обработки события onsubmit возвращает false. Почти такой же эффект будет достигнут, если вместо реакции на событие onsubmit формы ввести реакцию на событие onclick кнопки submit. Следующий вариант демонстрирует этот способ реагирования на попытку отправки данных на сервер. Их проверка также осуществляется клиентским скриптом. Из тега <form> уберите атрибут onsubmit="return TestValid();". Добавьте атрибут onclick="TestValid();" в тег <input type="submit">. Измените текст функции TestValid, как показано ниже. function TestValid() { var o = myForm.name; if (o.value == "") { document.getElementById("msg").innerText = "Please, enter a name"; o.focus(); event.returnValue = false; } } Обратите внимание на то, что значение false теперь присваивается свойству returnValue объекта event, а не атрибуту onsubmit, как было в предыдущем варианте. Да и событие теперь другое. В последних двух примерах намеренно были использованы различные способы доступа к элементам управления на стороне клиента. Сравните: document.all.namedItem("name") и myForm.name, или Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 document.getElementById("msg") и document.all.msg. Коллекция ссылок document.all (на все объекты документа HTML) существует только в JScript версии Microsoft. 1.9. Элементы управления на стороне сервера Итак, вы получили первый опыт разработки серверных Web-приложений, который показал, как использовать элементы HTML, а также скрипт, работающий как на стороне сервера (<% %> или <script runat="server">), так и на стороне клиента (<script> без атрибута runat="server"). Теперь нам предстоит сделать еще один шаг в изучении ASP.NET — освоить логику работы новых элементов управления: HTML Server Controls и Web Server Controls (их также называют Web Form Controls). По определению серверным элементом управления (server control) назывется класс, который прямо или косвенно происходит от класса System.Web.UI.Control. Представляя технологию ASP.NET, компания Microsoft утверждает следующие преимущества новых элементов управления, работающих на стороне сервера. Последовательность. Логика работы с новыми элементами весьма схожа с той, что используется при работе с традиционными элементами оконных форм. Обратная совместимость. Код на стороне сервера подстраивается под старые типы браузеров. Совместимость с новыми устройствами. Код на стороне сервера способен работать с браузерами новых устройств: WAP-Phones (Wireless Application Protocol), PDA (Personal Digital Assistants) и т.д. Продолжая обоснование выгод новой технологии, Microsoft приводит такие аргументы. Все серверные элементы имеют привычные свойства, например, TextBox имеет атрибут Text (а не value, как его аналог <input type="text"> в HTML). Все серверные элементы реализуют модель обработки событий, которая уже знакома разработчикам оконных приложений C#. Код на стороне сервера способен производить как чистые HTML-документы, так и документы с вставками JScript. Некоторые элементы способны распознавать новые устройства и производить код на языке WML. Это происходит, когда клиент вместо браузера использует мобильное устройство (WAP). Клиент получает только HTML-content и вставки JScript. Его source-код не содержит серверных элементов управления. Рассмотрим пример, который воспроизводит логику предыдущих страниц, но делает это с помощью элементов типа HTML Server Controls. Любопытно, что если в объявление любого элемента добавить атрибут runat="server", то он из обычного HTML-элемента превращается в серверный. Создайте новый файл Intro09.aspx и введите в него код, приведенный ниже. <%@ Page Language="C#" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Using HTML Server Controls</title></head> <body> <!-- #Include File="MyInclude.txt" --> <form action="Intro04.aspx" method="post" runat="server" id="myForm"> <h3>Name: <input id="name" type="text" runat="server" value="Alex Black" /> <br/><br/>Language: <select size="1" runat="server" id="list"> <option>Assembler</option><option>C++</option> <option selected="selected">C#</option><option>PL/I</option> </select> <input type="submit" value="OK" runat="server" /></h3><br/> <input style="border:none;" readonly="readonly" type="text" size="40" value="<%=name.Value%> selected <%=list.Value%>" /><br/> </form></body></html> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Include-файлы Если вы хотите многократно повторять один и тот же фрагмент HTML-текста, вставляя его в различные документы aspx, или несколько раз в один и тот же документ, то вы можете использовать технику Includeфайлов. Покажем, как это делается. Создайте в папке с последним документом новый текстовый файл, например, MyInclude.txt. Введите в него произвольный фрагмент HTML-текста: <span style="font-size:large; font-family:Tahoma; color:red;"> Query Form </span>(included from file: MyInclude.txt) Теперь вы можете вставлять этот фрагмент (весь текст файла) произвольное количество раз в произвольные места произвольных aspx-документов, используя особый синтаксис. Как ни странно, вставку выполняет оператор, вложенный внутрь комментария! <!-- #Include File="My.txt" --> Этот прием используется очень часто. Он позволяет сократить время на копирование стандартных фрагментов, которые повторяются от страницы к странице. Произведите операцию с любым документом и убедитесь, что техника Include-файлов работает. Измените расширение файла (обычно его задают равным INC) и убедитесь, что оно не имеет значения (не забудьте изменить ссылки). Страница работает, даже если заменить расширение на exe. Рис. 1.33. Иллюстрация техники вставки фрагментов текста (Include-файлы) Напомним, что вы можете закомментовать выделенный фрагмент кода с помощью клавиш ускорения ввода Ctrl+E C с семантикой EditComment. Проверьте работу страницы обычным способом. Заметили ли вы различия в работе страницы по сравнению с тем, что было ранее? Серверный элемент input помнит свое состояние, а обычный — нет. Проверьте работу при отсутствии атрибута runat="server" для формы, но оставьте его для других элементов управления. Уберите этот атрибут для других элементов, но оставьте его для формы. Смело вносите изменения, вы всегда можете вернуться назад (или вперед) с помощью Ctrl+Z и Ctrl+Y (Undo-Redo). Некоторое объяснение наблюдаемому поведению можно найти, просмотрев код, выполняемый на стороне клиента. Для этого в браузере дайте команду View→Source и вы увидите код, приведенный ниже. <html xmlns="http://www.w3.org/1999/xhtml"> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <head><title>Using HTML Server Controls</title></head> <body> <span style="font-size:large; font-family:Tahoma; color:red;"> Query Form </span>(included from file: MyInclude.txt) <form name="myForm" method="post" action="Intro09.aspx" id="myForm"> <div> <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTY0NTk1OTkwNWRk/ZdwjYLp0+eIf6h5IZt0XTz1mUM=" /> </div> <h3>Name: <input name="name" type="text" id="name" value="Alex Black" /><br/><br/> Language: <select name="list" id="list" size="1"> <option value="Assembler">Assembler</option> <option value="C++">C++</option> <option selected="selected" value="C#">C#</option> <option value="PL/I">PL/I</option> </select> <input name="ctl00" type="submit" value="OK" /></h3><br/> <input style="border:none;" readonly="readonly" type="text" size="40" value="Alex Black selected C#" /><br/> <div> <input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWBALeo4dFAvu49B0C75y+1w4CocCJjQupSw6xrmfzvF1mlqPjSsITxKQb6Q==" /> </div> </form></body></html> Здесь вы видите пару новых input-элементов (имеющих тип hidden). Их сгенерировал сервер. Один из спрятанных элементов назван __VIEWSTATE. Его значение (value) в зашифрованном виде хранит состояния всех серверных элементов. Элемент __VIEWSTATE был сгенерирован на сервере с помощью объекта класса StateBag, инкапсулирующего функциональность главного хранилища данных страницы. Каждый серверный элемент имеет свойство ViewState (наследство класса Control). Оно дает доступ к коллекции StateBag, которая хранит в виде символьных строк пары вида (name, value). Последние отражают текущее состояние всех серверных элементов управления, имеющихся на странице. В .NET 2.0 коллекция пар построена по типу System.Collections.Specialized.HybridDictionary (аналог HashTable), и дает возможность программным способом управлять своим содержимым. Состояния этих элементов (в сжатом виде) сохраняются в объекте класса StateBag. В HTML-тексте содержимое всей сумки состояний приведено в символьном формате. Обратите внимание на элемент input id="name". Сравните этот фрагмент (клиентского) markup-кода с тем, что приходит в браузер при отсутствии атрибута runat="server" (для этого же поля). В этом случае элемент превращается в обычный HTML control и его состояние не попадает в сумку __VIEWSTATE и, поэтому просле отсыла данных на сервер поле не восстанавливается. Также обратите внимание на то, что ядро ASP.NET (ASP.NET engine) автоматически идентифицирует все элементы управления на стороне сервера (см. name="ctl00" id="ctl00", и т.д.). Обратите внимание на следующие детали: Последнему элементу <input> нельзя присвоить атрибут runat="server" (проверьте это), так как скриптовые вставки вида <% %> для серверных элементов не работают. Они воздействуют только на свойства обычных элементов HTML. Переменная name внутри скрипта — это ссылка на объект класса HtmlInputText. Этот объект был создан ядром ASP.NET во время компиляции документа. Он работает на стороне сервера, его имя выбрано на основе, заданного нами идентификатора: id="name". Переменная list—это ссылка на объект класса HtmlSelect, который тоже был создан во время компиляции. Имя выбрано на основе идентификатора id="list". Все классы, поддерживающие элементы типа HTML Server Controls происходят от абстракного класса HtmlControl, который дает своим детям множество свойств (ID, Page, Style, ...) и методов (FindControl, DataBind, ...). Они позволяют управлять элементами стандартным способом. Например, класс Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 HtmlImage будет поддерживать любой элемент вида <img runat="server" />. Кстати, элемент img (так называемый empty element) ранее не требовал закрывающего тега. Классы (HtmlForm, HtmlTable, HtmlTableRow и т. д.), поддерживающие элементы контейнерного типа (<form>, <table>, <tr>, <td>, <a>, <select>), происходят от класса HtmlContainerControl, который, в свою очередь, является производным от HtmlControl. Эти элементы отличаются тем, что в них можно вкладывать другие элементы и они всегда требуют наличия закрывающего тега. Класс HtmlContainerControl развивает функциональность HtmlControl, предоставляя новые свойства: InnerHtml, InnerText. Класс HtmlGenericControl, производный от HtmlContainerControl, поддерживает элементы, для которых не было создано отдельных классов (например, <body>, <meta>, <span>, <div>, <p>). Если вы в тег <body>, добавите атрибут runat="server" и обязательно идентифицируете его, добавив атрибут id (например, id="myBody"), то на стороне сервера его будет поддерживать объект myBody класса HtmlGenericControl. Это дает возможность управлять элементом на стороне сервера, например: protected void Page_Load(object sender, EventArgs e) { myBody.Attributes["style"] = "background-color:#ffffcc;"; } Проведите опыт. Не трогая код метода Page_Load, переносите пару атрибутов (runat="server" id="myBody") из элемента в элемент, как бы примеряя к нему атрибут style. Советую сравнить действие атрибута на такие элементы, как: <span>, <div>, <p>, <h1>. Только с помощью этого опыта я понял, чем отличаются контейнеры <span> и <div>. 1.9.1. Элементы вида Literal Содержимым элементов типа <asp:Literal> является обычный код разметки. Управляя на сервере его свойством Mode (режим), мы можем изменять режим отображения. Перечисление LiteralMode содержит три константы, которые соответствуют трем режимам отображения свойства Text. Значение PassThrough свойства Mode говорит о том, что код разметки должен отправиться в браузер без изменений. Значение Encode говорит о том, что код разметки должен быть преобразован так, чтобы сами теги разметки были видны пользователю. Например, тег <br/> будет трансформирован в &lt;br/&gt;. Значение Transform говорит о том, что выходной код должен зависеть от типа браузера. Если он поддерживает стандарт XHTML, то код отправится без изменений, если — нет (например, браузер мобильного компьютера поддерживает WML), то из кода будут удалены те теги, которые не могут быть воспроизведены браузером. Следующий пример LiteralControl.aspx иллюстрирует функциональность элемента Literal. <%@ Page Language="C#" %> <script runat="server" type="text/C#"> protected void Page_Load(object sender, EventArgs e) { res.Text = "<h4>This text was created at server side</h4>" + "<b>This conrol</b>&nbsp;&nbsp;<input type='button' value='OK'/>" + "&nbsp; was <b>also</b> created there"; res.Mode = radioEncode.Checked ? LiteralMode.Encode : LiteralMode.PassThrough; } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Literal Control</title> <link rel="Stylesheet" href="../StyleSheet.css" type="text/css" /> </head> <body> <form id="form1" runat="server"> <div><h2>Literal Control Demo</h2> <asp:Literal ID="res" runat="server"></asp:Literal><br /><br /> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <asp:RadioButton ID="radioEncode" runat="server" GroupName="myRadio" Text="Encode" AutoPostBack="True" /><br /> <asp:RadioButton ID="radioPassthrough" runat="server" GroupName="myRadio" Text="PassThrough" AutoPostBack="True" Checked="True" /> </div></form></body></html> 1.10. Фазы жизненного цикла элементов управления Сервер загружает страницу ASP.NET по каждому требованию клиента и вновь выгружает ее по завершении запроса. Постоянная связь между клиентом и сервером отсутствует. Несмотря на это, ядро ASP.NET пытается создать иллюзию наличия постоянной связи. Все элементы управления ведут себя так, как-будто эта связь не прерывается — они поддерживают свои прежние состояния. Для того, чтобы правильно программировать поведение элементов управления, необходимо иметь представление о реальном жизненном цикле элементов. Следующая таблица дает представление о фазах этого цикла. Последний столбец содержит имя события или метода класса, которые надо переопределить, чтобы повлиять на состояние элемента. Фаза Что делает элемент управления Что переопределить Initialize Переходит в начальное состояние (производит начальные установки свойств). Init event (OnInit method) LoadViewState В конце этой фазы автоматически заполняется коллекция состояний (свойство ViewState). LoadViewState method Process postback data Обрабатывает данные, необходимые форме. Справедливо не для всех элементов (только для тех, которые реализуют интерфейс IPostBackDataHandler). LoadPostData method Load Выполняет действия, заданные кодом. Справедливы для всех элементов. В этот момент их состояния уже соответствуют начальным установкам. Load event method) Уведомления об изменениях Возбуждают события (raise events) в ответ на изменившиеся состояния. Справедливо для элементов, которые реализуют интерфейс IPostBackDataHandler. RaisePostDataChangedEvent method Handle postback events Обрабатывает (на стороне клиента) те события, которые вызвали postback уведомления, а также возбуждает события на стороне сервера. Справедливо для элементов, которые реализуют интерфейс IPostBackDataHandler. RaisePostBackEvent method Prerender Обновляет состояние перед тем как произвести преобразование (rendering) северного элемента в HTML-эквивалент. Изменения в этой фазе еще можно сохранить, а после вывода (фазы Render) уже нельзя. PreRender (OnPreRender) Save state Сохраняет состояние (ViewState property) в шифрованной текстовой строке, которая путешествует между сервером и клиентом в скрытой (hidden) переменной. Для повышения эффективности можно переопределить SaveViewState и изменить ViewState. SaveViewState method Render Генерирует результирующие состояния, отображаемые (rendered) HTMLэквивалентом. Render method Dispose Освобождает ресурсы перед своим уничтожением. Здесь надо освобождать критические ресурсы. Например, разрывать соединения с базой данных. Dispose method Unload Последняя возможность освободить ресурсы. Обычно не используется. Unload (OnUnload) (OnLoad При переопределении метода (см. правую колонку таблицы) не забывайте вызвать родительску версию (base.OnLoad();). Следующий рисунок (рис. 1.34) в более грубом масштабе, чем таблица, отображает последовательность происходящих событий. Он, по сути, изображает жизненный цикл Web-страницы, управляемой классом Page. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.34. Последовательность обработки событий на сервере Рассмотрим пример Intro10.aspx, который иллюстрирует способы переопределения виртуальных методов класса Page. <%@ Page Language="C#" %> <script language="C#" runat="server"> void Print (string s) { message.InnerHtml += s + "<br>"; } protected override void OnInit(EventArgs e) { base.OnInit(e); Print("Page: OnInit"); } protected override void OnLoad (EventArgs e) { base.OnLoad (e); Print("Page_Load: " + (IsPostBack ? "Post Back" : "First time")); } protected override void OnPreRender(EventArgs e) { base.OnPreRender(e); Print("Page: PreRender"); } protected override object SaveViewState() { object state = base.SaveViewState(); Print("Page: Saving View State<br/>-----------------------------"); return state; } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Test Events</title></head> <body><form runat="server" id="myForm" action="Intro10.aspx"> <table style="background-color:Silver; font-weight: bold; font-size: 10pt; border: groove; font-family: Arial;" id="myTable" runat="server"> <tr><td>Message :</td> <td><span runat="server" id="message"/></td></tr> <tr><td><input type="submit" /></td></tr> </table> </form></body></html> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Нажимая кнопку Submit, убедитесь, что фазы жизненного цикла страницы отображаются в ячейках таблицы. Здесь важно уяснить порядок возбуждения событий на стороне сервера и понять, что при повторных обращениях к странице он несколько отличается от первоначального. Событие Init не возбуждается, а повторную загрузку страницы можно выявить с помощью свойства IsPostBack класса Page. Далее мы будем часто использовать этот прием. Атрибут Trace Чтобы получить еще более подробную информацию о том, что происходит на сервере с документом ASPX, измените начальную директиву, как показано ниже, и запустите страницу на выполнение. <%@ Page Language="C#" Trace="true" TraceMode="SortByTime" %> Атрибут Trace способен включить (или выключить) режим трассировки всех событий на сервере. Кроме деталей процесса обработки, вы видите множество коллекций, которые используются рабочим процессом ASP.NET. Некоторые из них вы уже знаете, другие (Session, Application, Headers, Request Cookies, Response Cookies) мы рассмотрим позже. Более подробную информацию о режиме трассировки вы можете получить в документации MSDN. 1.11. Стили элементов из категории HTML Server Controls Динамика поведения Обычные элементы HTML легко оживить с помощью скрипта, работающего на стороне клиента, и мы уже делали это не один раз. Первые, прямые попытки добиться такого же поведения от серверных элементов часто терпят неудачу, так как логика их использования по умолчанию отличается от логики использования обычных элементов. Тем не менее, серверные элементы позволяют изменять свои атрибуты (на стороне клиента) и вы можете добиться от них такого же поведения, которое легко добавить их аналогам — обычным элементам. Рассмотрим сначала, как оживляются элементы типа HTML Server Controls. Когда ASP.NET-страница проходит фазу синтаксического разбора (is parsed), информация о стилях элементов попадает в коллекцию стилей типа CssStyleCollection, доступ к которой обеспечивает свойство Style класса System.Web.UI. HtmlControls.HtmlControl. Каждый объект этого класса имеет свойство Style, которое дает доступ к специализированной коллекции типа CssStyleCollection. Коллекция CssStyleCollection представляет собой ассоциативный массив (dictionary), хранящий атрибуты стиля в виде пар (key, value). Специализированные коллекции отличаются от фундаментальных тем, что они настроены на хранение определенных типов данных, в то время как фундаментальные—хранят произвольные типы данных (object). Специализированные коллекции требуют нестандартного подхода. Например, коллекция типа CssStyleCollection, в отличие от стандартной HashTable, имеет свойство Keys, но не имеет свойства Values. Она также не имеет перечислителя. Покажем, как управлять такой коллекцией. Предположим, вы вставили в форму стандартный HTML Server Control типа: <span id="sp" runat="server"></span> Это контейнер текстовых строк и изображений, которому можно приписать множество стилей. Традиционным способом изменения стандартных стилей CSS является использование атрибута style, например: <span id="sp" runat="server" style="display:block; border:'0.3cm ridge red';"></span> Таблица стилей Style типа CssStyleCollection заполняется данными, взятыми из списка пар (key, value). Пары разделены символами ';'. Текстовые строки "display" и "border" по сути являются ключами коллекции, а "block" и "0.3cm ridge red" — соответствующими им значениями. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Другим способом задания стилей элемента является прямое обращение к коллекции с помощью методов класса CssStyleCollection. Это можно делать внутри серверного скрипта. Следующий пример показывает, как заполнить еще три позиции коллекции стилей элемента sp: void Page_Load (object sender, EventArgs e) { if (!IsPostBack) { sp.Style ["width"] = "250px"; sp.Style ["height"] = "120px"; sp.Style.Add("background", "ivory url(../Images/Cone.jpg) no-repeat center center"); } } Компилятор автоматически создал объект sp класса HtmlGenericControl. Он это сделал только потому, что он обнаружил в элементе <span> атрибут runat="server". Если убрать этот атрибут из кода разметки, то объект sp исчезнет. Все элементы с атрибутом runat="server" поддержаны на стороне сервера объектами соответствующих классов, которые были перечислилены ранее. Первые два оператора являют пример записи в коллекцию Style. Синтаксис имеет такой вид: collection[key] = value; Третий оператор обращается к методу Add. Синтаксис имеет такой вид: collection.Add (key, value); Некоторые значения стилей являются комбинациями, определяющими сложный стиль. Например, строка: "ivory url(../Images/Cone.jpg) no-repeat center center" определяет стиль background (фона элемента) как комбинацию пяти строк текста. Эти строки определяют компоненты стиля при закраске фона панели span. ivory — задает цвет фона (слоновой кости), url(../Images/Cone.jpg) — задает источник изображения (оно берется из файла Cone.jpg), no-repeat — определяет способ отображения. Если размер окна превышает размер изображения, то оно не повторяется. По умолчанию оно будет повторяться. center — определяет способ размещения изображения (центр по горизонтали и по вертикали). Обычно все страницы сайта используют множество изображений, расположенных в папке Images. Создайте такую папку и поместите в нее изображения из папки с материалами курса. После этого в контекстном меню папки Images дайте команду Add Existing Item, выделите все изображения и нажмите кнопку Add. После этого механизм IntelliSense будет автоматически следить за тем, чтобы ссылки указывали на существующие изображения. Таблица каскадных стилей Существует еще один способ временной отмены стандартного стиля элемента. Он осуществляется с помощью атрибута class и называется sub-classing. Исследуйте этот способ в каком-нибудь документе HTML, (не ASPX). Атрибут class можно применить к элементу любого типа. Сначала создается таблица именованных стилей (CSS). Например: <style type="text/css"> <!-.red { color:red; font-weight:bold; font-size:larger; text-align:center; } .bar {background:#336699; width:100%; padding: 5px; border-top:1px solid #99CCFF; border-bottom:1px solid #000000;} .title {font-size:15pt; color:white; padding-left:10px;} --> </style> Затем добавляется элемент, который использует каскадный стиль (например, с именем red). <p class="red">Classy, red, bold, and centered</p> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Стандартному тегу <p> временно приписан стиль red. Сам стиль red должен быть определен заранее. Имея определение стиля, его можно использовать для коррекции стиля любых других элементов: <span class="red" id="sp" runat="server" style="display:block;border: '0.3cm ridge red'">This text is inside a styled span control </span> Обратите внимание на следующие детали: При определении нового именованного каскадного стиля перед его именем должна стоять точка, Компоненты (атрибуты) стиля перечислены в виде пар key:value; внутри блока { }. Знаки комментария <!-- и --> необходимы лишь старым браузерам (далее эти знаки мы будем опускать), Компоненты стиля, заданные атрибутом style, будут работать при отображении элемента и попадут в коллекцию CssStyleCollection элемента span. Компоненты стиля, заданные атрибутом class, также будут работать при отображении элемента, но они не попадут в коллекцию CssStyleCollection (?!). Задание. Создайте страницу Intro11.aspx, иллюстрирующую возможности управления стилями элементов типа HTML Server Controls, как показано на рис. 1.35. Рис. 1.35. Управление компонентами стиля HTML Server Controls Для того, чтобы исследовать динамику процесса изменения стилей, найдите изображение небольшого размера, принесите его в папку Images и переименуйте файл в Cone.jpg (или замените код модуля, приспособив его под имя вашего файла). Следующий материал поможет вам в решении поставленной задачи. Прочтите до конца раздела, и только затем приступайте к работе, иначе некоторые детали могут вызвать затруднения. Логика работы страницы такова: Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Все элементы управления на странице — это HTML-Server Controls. Вспомните, что это означает. Все элементы управления страницы призваны изменять компоненты стиля одного и того же элемента span (панель с картинкой). Элемент span — это испытуемый элемент (или ваш подопытный кролик). Вот он — контейнер строк и изображений, компонетами стиля которого будем управлять . <span id="sp" runat="server"></span> Кнопки типа <input type="radio" (на красной панели) изменяют значение атрибута display испытуемого элемента span. События нажатия этих кнопок должны быть обработаны на стороне клиента. Используйте для этого технику DHTML, то есть просто присваивайте стилю нужное значение (например: onclick="sp.style.display='inline';"). События нажатия кнопок типа button (три кнопки внизу страницы) должны обрабатываться на стороне сервера. Введите реакцию на событие согласно схеме: onserverclick="имя метода обработки". Сам элемент span должен реагировать на мышиные события (onmouseover и onmouseout) и изменять стиль своей рамки на стороне клиента. Вновь примените технику DHTML. Собирая страницу из частей, не забудьте, что все они должны располагаться на форме с атрибутом: runat="server". Как добраться до компонентов стиля в коде JScript на стороне клиента Мы достаточно часто пользовались этим приемом. Традиционным способом изменения стандартных стилей является использование атрибута style, например: <span id="sp" runat="server" style="display:block; border:'0.3cm ridge red';"> Множество компонентов стиля заполняется данными, взятыми из списка пар (key, value). Здесь идет речь о коллекции, работающей на стороне клиента, то есть в ходе выполнения (или отображения) страницы браузером. Как добраться до компонентов стиля в коде C# на стороне сервера Каждый объект класса HtmlControl, (а следовательно, и все элементы типа HTML Server Controls) имеет свойство Style, которое дает доступ к коллекции типа CssStyleCollection. Отметьте, что здесь идет речь о коллекциях, работающих на стороне сервера, то есть в коде класса Page (на языке C#), поддерживающего нашу страницу на стороне сервера. Другим способом задания компонентов стиля элемента является прямое обращение к коллекции каскадных стилей на стороне клиента (class="red"). На стороне сервера следует использовать свойство CssClass, например, msg.CssClass="red";. Учтите, что в однофайловой модели разработки приложений серверный код раполагается в файле aspx (а не в отдельном файле с кодом поддержки страницы — aspx.cs). Серверный скрипт Для управления компонентами стиля на стороне сервера можно использовать переменные и методы класса, производного от класса Page. Сам класс и его объект автоматически создаются при компиляции файла aspx. Все, что вы вставите внутрь блока: <script language="C#" runat="server"> является содержимым этого класса (производного от Page). Сам класс в однофайловой модели разработки приложений присутствует неявно. Вы не видите его объявления, но он создан и готов работать. Например, вы можете ввести в этот класс переменные (repeat и noRepeat), как показано ниже и использовать их для управления одним из компонентов стиля. <%@ Page language="c#" AutoEventWireup="true"%> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>HTML Server Control Styles</title> <script language="C#" runat="server"> public static string Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 repeat = "ivory url(../Images/Cone.jpg)", noRepeat = repeat + " no-repeat center center"; . . . . . . . Дополнить коллекцию парой вида (name, value) можно так. sp.Style.Add("background", noRepeat); Для вывода компонентов стиля можно использовать такой метод класса Page. void ShowStyles() { Response.Write ( "<div class='title bar' style='position:relative; top:0; left:0;'>" + "Initial Span styles<br/></div><br/><blockquote>"); //======= Здесь мы работаем с коллекцией компонентов стиля foreach (string key in sp.Style.Keys) Response.Write(key + ": " + sp.Style[key] + "<br/>"); Response.Write("<br/></blockquote>"); } Вызывайте его после каждого изменения стиля, происходящего на стороне сервера. Атрибут class='title bar' предполагает, что в таблице стилей имеются именованные стили title и bar. Результаты работы метода ShowStyles вы видите на рис. 1.35 (в верхней части окна браузера). Далее можно добавить метод RemoveBkgnd для удаления одного из компонентов стиля и метод AddBkgnd для его вставки. Учтите, что эти методы вызываются в ответ на события (являются заданиями делегатов), поэтому они должны иметь определенную сигнатуру: void ИмяМетода (object sender, EventArgs e) Для (неявного) создания делегатов — введения реакций на нажатия кнопок, используйте атрибут onserverclick. Например. <button class="butn" id="bRemove" onserverclick="RemoveBkgnd" runat="server" . . . Обратите внимание на то, что здесь не надо ставить круглые скобки, как вы это делали при связывании функций с событиями в коде на JScript. Здесь имя функции — означает ее адрес, а в обработке событий JScript — вызов. Поэтому при обработке событий на стороне клиента скобки необходимы, например, onclick="DoSomething()". Панель, созданная в коде на стороне сервера Все элементы управления (документа ASPX) удобно размещать на отдельных панелях. Панель — это элемент типа <div>. У нас их — несколько. В верхней части окна браузера вы видите синюю панель, которая создана в коде на стороне сервера, другие панели жестко прописаны в тексте документа ASPX. Создать панель на стороне сервера можно так. Response.Write ("<div class='title bar' style='position:relative; top:0; left:0;'>" + "Initial Span styles<br/></div><br/>"); В верхней части окна (под синей панелью) вы видите строки текста, соответствующие компонентам стиля элемента span. Список стилей выводит функция ShowStyles. Она вызывается после каждого изменения стиля, происходящего на стороне сервера. Отметьте, нам доступны только те компоненты стиля, которые попали в коллекцию CssStyleCollection. Панели, созданные в коде разметки В средней части окна браузера вы видите панели, созданные в коде разметки (не в коде серверного скрипта). Например, <div class="title radiobar">Span Position: Здесь расположен текст или другие HTML-элементы </div> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Вопросы Анализируя код и поведение страницы, попробуйте ответить на такие вопросы: Почему изменения в стиле background с помощью кнопки Toggle Repeat Background отображаются в списке стилей (вверху окна), а изменение стилей display и border не отображаются? Будет-ли работать страница, если убрать теги <form></form>? Как изменится ее поведение? Где происходят изменения каждого из стилей (на стороне сервера, или на стороне клиента)? Установите компонент стиля display в значение none, а затем нажмите любую из зеленых серверных кнопок. Элемент span вновь появляется в окне браузера и это противоречит установке стиля display. Почему это происходит? Ответы Изменения в стиле с помощью кнопки Toggle Repeat Background происходят на стороне сервера (см. событие onserverclick), что приводит к вызову метода ShowStyles, а изменения стилей display и border осуществляются на стороне клиента с помощью технологии DHTML (события onmouseover и onclick). Все реакции на стороне сервера перестанут работать (появятся ошибки времени исполнения). Причина: отсутствует скрытая переменная __VIEWSTATE и коллекция состояний. Реакции на стороне клиента будут работать так же, как и раньше. Компоненты стиля border и display изменяются на стороне клиента, а background — на стороне сервера. Клиентский код работает с HTML-образом серверного элемента, а не с самим элементом. Установки стиля на стороне клиента не могут быть оперативно отображены на стороне сервера. При каждом обращении к серверу (событие Postback) целесообразно корректировать значение свойств, измененных на стороне киента. Итак, мы рассмотрели, как преобразовать простые элементы (HTML Controls) в эквивалентные им, но более функциональные, серверные элементы (HTML Server Controls). Для того, чтобы превратить обычный элемент HTML (например, выпадающий список <select>) в HTML Server Control, достаточно добавить в его объявление атрибут runat="server". Этот атрибут сообщает подсистеме ASP.NET, что элемент теперь является не простым, а серверным. Установка атрибута запускает механизм поддержки элемента на стороне сервера. Все серверные элементы управляются с помощью классов, имеющих множество свойств и методов. Элемент <select> поддержан классом HtmlSelect. Один из вариантов решения задачи, поставленной выше, вы найдете в папке с материалами курса. Внешний шаблон стилей Блок каскадных стилей <style></style>, который был введен в предыдущем документе, представляет собой один из способов управления обликом выходного HTML-документа. Рассмотрим другой способ подключения множества каскадных стилей к конкретному документу. Добавьте к проекту новый элемент с помощью команды: Add New Item→Style Sheet, выбранной в его контекстном меню. Студия создаст файл с именем StyleSheet.css (если вы согласились с установками по умолчанию). Введите в файл следущее множество стилей. Некоторые из них пригодятся нам позже. body { font-family:Arial; font-size:80%; } table { border:1px solid; border-color:Black; } th { padding:2px; background-color:#555555; color:Lime;} a { text-decoration:none; color: blue; } .red { color:red; font-weight:bold; font-size:larger; text-align:center; } .bar {background:#336699; width:100%; padding: 5px; border-top:1px solid #99CCFF; border-bottom:1px solid #000000;} .title {font-size:15pt; color:white; padding-left:10px;} .radiobar {background:#ff2222; padding: 10px; border-top:1px solid #CCCCCC; border-bottom:1px solid #000000;} .butn {background:#aaffaa; width:200; font-family:Arial; font-size:90%;} .black { padding:2px; background-color:#555555; color:White; } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 .myTD { font-size:90%; background:#ffffdd; } .zoom { font-size: 120%; background:#ffffdd; } .pos250 { position:absolute;left:250px; width: 342px; color:Lime; } .pos110 { position:absolute;left:110px; width: 125px; } .darkShadow {background-color:ThreeDDarkShadow; color:White; font-weight:700; padding-top:2px; padding-bottom:15px; padding-left:15px; padding-right:15px;} .green { font-family:Comic Sans MS; color:green; } .big { font-size: 14pt; text-align: center } При вводе текста работает механизм подсказок (IntelliSense). В студии 2008 IntelliSense работает везде. В студии 2005 он не работал при работе с встроенными блоками <style></style>. Для подключения множества стилей из файла StyleSheet.css к произвольному документу, вставьте внутрь тега <head> этого документа тег <link>, который ссылается на файл с каскадными стилями: <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> Указанная строка кода может автоматически быть сгенерирована студией, если вы притащите файл StyleSheet.css из окна Solution Explorer на поверхность документа в режиме дизайна, или выберете css-файл в окне свойств элемента DOCUMENT (имя свойства StyleSheet). Очевидно, что, накопив библиотеку CSS-файлов, мы можем быстро и эффективно изменять облик любого документа нашего сайта. Более того, мы можем копировать понравившиеся шаблоны или файлы с шаблонами из множества HTML-страниц, доступных через Internet. 1.12. Элементы типа Web Server Controls Другая группа серверных элементов (Web Controls) является следующим шагом вперед по пути развития технологии ASP.NET. Элементы этой группы довольно точно отображают поведение обычных элементов HTML, но имеют значительно большую функциональность. Они имеют префикс asp:, который расширяется, как макрос и обозначает принадлежность к пространству имен System.Web.UI.WebControls. Это пространство автоматически подключено ко всем ASP.NET страницам, поэтому его нет необходимости импортировать. Различают 4 подмножества серверных элементов. Встроенные элементы. При генерации клиентского кода они отображаются в обычные элементы HTML. Примеры: asp:TextBox, asp:ListBox, asp:ListView, asp:Button, asp:CheckBox, asp:Table. Элементы проверки синтаксиса и диапазона вводимой информации (Input Validation controls). Примеры: asp:RangeValidator, asp:RegularExpressionValidator. Элементы с более богатым интерфейсом (Rich controls): asp:Calendar, asp:AdRotator. Элементы отображения данных: asp:DataGrid, asp:GridView, asp:Repeater, asp:DataList. Следующая таблица показывает соответствие между некоторыми элементами типа Web Controls и элементами HTML Controls. Web control HTML control <asp:TextBox> <input type=text> <asp:Button> <input type=submit> <asp:ImageButton> <input type=image> <asp:CheckBox> <input type=checkbox> <asp:RadioButton> <input type=radiobutton> <asp:ListBox> <select size="5"> </select> <asp:DropDownList> <select> </select> <asp:Hyperlink> <a href="…"> </a> <asp:Image> <img src="…"> <asp:Label> <span> </span> <asp:Panel> <div> </div> <asp:Table> <table> </table> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рассмотрим примеры, использующие Web Controls. Создайте документ Intro12.aspx и введите в него следующий код: <%@ Page Language="C#" AutoEventWireup="true" %> <script language="C#" runat="server"> void Show(object sender, EventArgs e) { if (string.IsNullOrEmpty(name.Text) || lang.SelectedItem == null) msg.Text = "Please, fill in the form"; else msg.Text = name.Text + " selected " + lang.SelectedItem.Text; } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Enable View State</title> <link rel="Stylesheet" href="../StyleSheet.css" type="text/css" /> </head> <body> <form runat="server" id="myForm"> <h3>Query Form</h3> Name: <asp:TextBox runat="server" ID="name" /><br /><br /> Language: <asp:ListBox runat="server" rows="1" ID="lang"> <asp:ListItem Text="Assembler"/><asp:ListItem Text="C++"/> <asp:ListItem Text="C#"/> <asp:ListItem Text="PL/I"/> </asp:ListBox> <asp:Button text="OK" runat="server" OnClick="Show"/><br /><br /> <asp:Label ID="msg" runat="server" /> </form> </body> </html> Заметьте, что форма не имеет атрибута action, а некоторые серверные элементы (типа Web Form Controls) не идентифицированы (не имеют атрибутов вида id). При обработке страницы ядро ASP.NET выполняет следующие преобразования: Создает атрибуты action="Intro12.aspx", method="post" так, чтобы данные формы сохранились и смогли вернуться назад (posted back). Если уникальные идентификаторы id отсутствуют, то они будут сгенерированы для всех серверных элементов формы. Если они указаны, то ядро воспользуется ими. Создает атрибуты name для всех серверных элементов формы. Они необходимы старым браузерам. Генерирует для каждого поля ввода атрибут value, значением которого является тот текст, который был в элементе в момент посылки (submit). Именно эта установка позволяет сохранить состояние элементов. Добавляет спрятанный элемент управления (hidden control) с именем __VIEWSTATE, который хранит состояния всех элементов формы. Проверьте работу страницы. Теперь при вводе имени, выборе строки из списка и отправки данных на сервер, поведение страницы вполне логично и предсказуемо. Просмотрите клиентский код (дайте команду View→Source) и убедитесь, что он такой же, как и в случае элементов типа HTML Server Controls. Обратите внимание на то, что ядро ASP.NET автоматически идентифицирует все элементы и заменяет их на HTML-эквиваленты. Например, элемент типа asp:ListBox, определенный на стороне сервера, на стороне клиента имеет вид обычного списка (select), который без осложнений может быть интерпретирован любым браузером. Просмотрите клиентский код и выясните, во что преобразовался элемент <asp:Button>. Это элемент, отправляющий данные на сервер. <input type="submit" name="ctl01" value="OK" /> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Поэтому, любая серверная кнопка (кнопка с атрибутом runat="server") по умолчанию отправляет данные на сервер. Запомните этот факт, он вам не раз пригодится. Все серверные элементы типа Web Controls помнят свое состояние, как и HTML Server Controls, но на стороне сервера им соответствуют другие классы. Например, HTML Server Control <select runat="server"> поддержан классом HtmlSelect, а аналогичный ему Web Control типа asp:ListBox поддержан классом ListBox. Реакции на события Нажатие серверной кнопки, как вы помните, собирает данные формы, отправляет их на сервер и запрашивает (requests) ту же самую страницу. Этот феномен называют Postback. Система вновь загружает страницу, вновь происходит событие Load класса Page и делегат вновь исполняет свое задание Page_Load. При работе с серверными элементами мы имеем ряд преимуществ. Мы можем вставить скрипт на языке С#, который будет реагировать на стандартные события. Для кнопки (asp:Button) создана реакция на событие Click. Мы добавили ее вручную. Но ее можно было создать двойным щелчком по кнопке в режиме дизайна, тогда она имела бы стандартное имя button1_ Clicked (что вызывает мое неудовольствие). Если бы мы задали id="OK", то реакция бы получила имя: OK_Clicked (что значительно лучше). В коде функции обработки события надо обращаться к элементам управления, а это выполнимо, только если они имеют идентификаторы. Мы пользуемся идентификаторами с заданной нами семантикой, а не теми, что автоматически бы были созданы студией. Такой подход полезно включить в постоянный арсенал программиста. Неприятно (и чревато ошибками) работать с элементами, имеющими идентификаторы: TextBox17 или Button32. Метод обработки события Click имеет ту же сигнатуру, что и в приложениях Windows Forms. Событие так же возбуждается при нажатии кнопки на форме, а функция его обработки также является методом класса (но теперь это класс, производный от Page, а не Form). Проверьте работу страницы. Она должна вести себя почти так же, как Intro08.aspx. Полезно сравнить коды двух файлов и убедиться, что сравнение не в пользу обычных HTML-элементов. Замените весь блок <asp:ListBox> . . . </ListBox> одной строкой кода: <asp:ListBox ID="lang" runat="server" rows="1" /> В блок <script> добавьте реакцию на событие загрузки страницы на сервере. void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string[] langs = { "C++", "Java", "Assembler", "C#", "PL/I", "Perl" }; foreach (string s in langs) lang.Items.Add(new ListItem(s)); name.Text = "Joe Doe"; lang.SelectedIndex = 0; } } Возможность реагировать на события совместно с мощью классов, поддерживающих серверные элементы, позволяют убрать статический код разметки из блока <asp:ListBox> </ListBox> и заменить его динамическим кодом начального заполнения списка. Этот код выполняется в момент загрузки (внутри функции Page_Load), также, как это принято делать в обычных оконных приложениях. Да, в настоящий момент мы загружаем список фиксированным множеством строк, но если приложить некоторые усилия, то эти строки можно взять из живого источника (например, изменяющейся во времени базы данных). При открытии страницы обратите внимание на то, что ее элементы инициализированы так, как указано в реакции на событие Load. Анализ свойства IsPostBack, который вы видите в теле функции Page_Load, позволяет выявить факт первоначальной загрузки. Это необходимо делать, так как инициализация должна Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 происходить один раз, а установка атрибута формы action (ее выполнил компилятор) повторяет обработку данных формы при каждом нажатии серверной кнопки. Смысл происходящего легче понять, если временно закомментировать if (!IsPostBack) (только одну эту строку кода) и сравнить поведение страницы при изменении данных и повторной загрузке документа с тем, что проявляется при наличии этого условия. Переменная __VIEWSTATE Значение (value) скрытого элемента <input> с id="__VIEWSTATE" представляет собой строку символов в формате base 64 digits, который является стандартом сериализации данных. Класс Convert позволяет декодировать ее и представить в виде символов ASCII. Формат ASCII используется в HTTP-запросах. Для иллюстрации этой возможности добавьте в конец формы следующий фрагмент. <br/><hr /><br/> Enter a raw __VIEWSTATE value: <asp:TextBox id="viewState" runat="server" /> <asp:Button runat="server" ID="bShow" Text="Decode" OnClick="OnDecode" /><br/> Decoded __VIEWSTATE value: <asp:Label runat="server" ID="lblShow" /> В обработчик OnDecode события нажатия кнопки следует вставить две строки кода. void OnDecode(object sender, EventArgs e) { byte[] bytes = Convert.FromBase64String (viewState.Text); lblShow.Text = Encoding.ASCII.GetString (bytes); } Если теперь скопировать в буфер обмена закодированное содержимое переменной __VIEWSTATE, восстановить его в окне TextBox, а затем нажать кнопку Decode, то в элемент lblShow сервер выведет декодированный ASCII-текст. Добавьте метод OnDecode в блок с серверным скриптом, Запустите страницу на выполнение, нажмите кнопку Submit, аккуратно скопируйте значение переменной __VIEWSTATE (это можно сделать либо из HTML-кода, воспроизводимого браузером, либо прямо из таблицы, отображаемой на странице), Восстановите закодированный текст в поле ввода TextBox viewState и нажмите кнопку Decode. Результат декодирования приведен на рис. 1.36. Вы видите декодированное значение скрытой переменной __VIEWSTATE. Среди непонятных символов, связанных с особенностью формата сериализации, вы видите содержимое списка и текст, который должен попасть в элемент <asp:Label ID="msg" runat="server" />. Заметьте, что в коде разметки ASPX его значение пусто. Это значение было сгенерировано на сервере и сохранено в переменной __VIEWSTATE. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.36. Декодирование текста переменной __VIEWSTATE Если вы добавите атрибут Trace="true" в директиву <%@ Page>: <%@ Page Language="C#" Trace="true" AutoEventWireup="true" %> то в списке всех элементов формы увидите, что переменная __VIEWSTATE используется для хранения состояний трех элементов страницы: lang, msg и lblShow (см. рис 1.37, колонку ViewState). Рис. 1.37. Отображение данных коллекции ViewState в режиме Trace="true" Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Элемент <asp:TextBox ID="name"> не нуждается в услугах переменной __VIEWSTATE, так как его значение автоматически формируется на сервере с учетом данных, отправляемых формой в процессе submit по схеме (методу) "post". Свойство EnableViewState Способностью запоминать содержимое элементов формы можно управлять. Для этого в форме (или в классе Page) имееется свойство EnableViewState. В качестве эксперимента попробуйте добавить внутрь метода Page_Load строку кода EnableViewState = false; и проверьте работу страницы. Вместо установки свойства EnableViewState для объекта Page, вы можете установить: myForm.EnableViewState = false; Результат будет тем же. Тот же эффект будет достигнут, если в коде разметки (для формы <form>) установить атрибут enableviewstate="true". Еще одна деталь. Вы помните, что любая серверная кнопка по умолчанию отправляет данные на сервер. Но если вы установите для нее свойство UseSubmitBehavior в значение "false", то она не будет этого делать. В этом и состоит преимущество серверных технологий. Вы имеете возможность на ходу, динамически, изменять свойства отправляемого клиенту HTML-документа. Клавиши ускоренного доступа Работая с формами Windows, вы, возможно, привыкли использовать клавиши ускорения доступа (accelerator keys). Например, при нажатии комбинации Alt+N фокус автоматически переходит в поле ввода с id="name". Теперь эта функциональность доступна и в ASP.NET. Серверный элемент asp:Label имеет атрибуты AccessKey и AssociatedControlID, которые позволяют добиться такого же поведения. Атрибут AccessKey задает код клавиши ускорения (без учета Alt), а атрибут AssociatedControlID — идентификатор поля ввода, в которое следует перевести фокус. Введите в документ изменения, которые иллюстрируют этот прием. Замените простой текст Name: серверным элементом <asp:Label> с таким же текстом. Например. <asp:Label runat="server" Text="Name:"/> Повторите эту операцию для строки текста Language:. Внутрь метки с текстом Name: добавьте атрибуты: AccessKey="N" AssociatedControlID = "name", а внутрь метки с текстом Language: AccessKey = "L" AssociatedControlID = "lang". Запустите страницу и введите комбинацию Alt+N, или Alt+L. Приведем новую версию формы (только формы), в которой использован этот прием. <form runat="server" id="myForm"> <h3>Query Form</h3> <asp:Label runat="server" AccessKey="N" AssociatedControlID="name" Text="Name:"/> <asp:TextBox runat="server" ID="name" /><br /><br /> <asp:Label runat="server" AccessKey="L" AssociatedControlID="lang" Text="Language:"/> <asp:ListBox runat="server" rows="1" ID="lang" /> <asp:Button text="OK" runat="server" OnClick="Show"/><br /><br /> <asp:Label ID="msg" runat="server" /> </form> Запустите страницу и введите комбинацию Alt+N, или Alt+L. Фокус (и выделение текста) послушно переходит в нужное поле ввода. Заметьте, что это происходит на стороне клиента (в браузере). Отметим, однако, что заслуга ускорения доступа к полям ввода принадлежит обычным HTML-элементам вида: <label for="tName" accesskey="N">Name: </label> Эти элементы сгенерированы ASP.NET-процессом, они-то и реализуют описанную функциональность. Серверный элемент <asp:Label> без атрибутов AssociatedControlID и AccessKey превращается в <span>, а при наличии атрибутов — в <label>. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Атрибут AutoPostBack Некоторые элементы управления на стороне сервера способны автоматически отсылать данные на сервер, не дожидаясь нажатия кнопки submit. Например, элемент ListBox имеет событие SelectedIndexChanged, которое способно вызвать событие типа PostBack (отсыл данных на сервер). Для проверки этой возможности добавьте атрибут AutoPostBack="true" внутрь элемента ListBox, или (что эквивалентно) установите в методе Page_Load свойство: lang.AutoPostBack = true;. Проверьте работу страницы и убедитесь, что этого недостаточно. Надо на сервере реагировать на событие выбора строки списка и выполнять тот код, который сейчас находится внутри метода Show. Поэтому добавьте в ListBox реакцию на событие SelectedIndexChanged. Заметьте, что вы можете выполнить это разными способами. Ввести обработку события вручную. Совершить двойной щелчок над ListBox в режиме дизайна. Рекомендую потренироваться и опробовать оба способа. Что творится за сценой Установка AutoEventWireup="true" в директиве <%@ Page %> говорит о том, что привязка адресов функций, обрабатывающих события, к самим событиям будет выполняться автоматически (в недрах того кода, который генерируется студией и прячется в папке Temporary ASP.NET files (мы говорили о нем ранее). Там будет создан делегат, его заданием будет ссылка на метод, обрабатывающий событие (его имя lang_SelectedIndexChanged). Для включения машины, увязывающей события с методами класса Page, надо, чтобы в серверном элементе (в нашем случае, это <asp:ListBox>) был атрибут: OnSelectedIndexChanged="lang_SelectedIndexChanged" Он неявно связывает событие с кодом его обработки. Как вы его создадите — вручную, или с помощью дизайнера, неважно. Но важно, чтобы в коде поддержки (блоке <script>) был метод с правильной сигнатурой. В нашем случае, она должна быть: protected void lang_SelectedIndexChanged(object sender, EventArgs e) Описатель protected неважен. Вы можете его опустить и по умолчанию метод будет скрытым. Описатель protected будет важен, если вы намерены создать класс, производный от класса нашей страницы. В рамках выбранной архитектуры (однофайловая модель) это делать неудобно, так как мы с трудом можем выяснить имя того класса, который поддерживает нашу страницу. Его имя автоматически вывела студия (оно имеет неприличный вид intro_intro12_aspx). Как я это выяснил? Поставьте фокус в любое место любого метода (например, внутрь метода Show), введите имя пространства имен ASP и поставьте точку. Механизм IntelliSense отзовется и покажет вам имя класса, живущего в этом пространстве имен. Итак, самый простой и надежный способ введения реакций на события — это работа в режиме дизайна с окном свойств при нажатой кнопке Events (см. заголовок окна свойств). Но, мы можем создать заготовку обработчика события по-другому: таким же способом, которым мы пользовались при разработке Windows Forms. Поставьте фокус внутрь ветви if (!IsPostBack) и введите текст: lang.SelectedIndexChanged +=. В этот момент, как и при разработке оконных приложений, включается IntelliSense и предлагает создать делегата, а потом и его задание (то есть, функцию обработки события). Нажмите два раза клавишу Tab и студия создаст функцию обработки события. Но эта функция не привязана к событию (если до этого вы не создали неявную привязку). Любопытно, что компилятор не сообщает о том, что код явной привязки является лишним. lang.SelectedIndexChanged += new EventHandler (lang_SelectedIndexChanged); Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Легко убедиться, что этот код не работает. Достаточно убрать из кода разметки атрибут: OnSelectedIndexChanged="lang_SelectedIndexChanged" Напомню, что это неявная привязка к событию. Насколько я помню, вариант явной привязки (при включенном флаге AutoEventWireup) не нравится студии 2005, но он проходит в студии 2008. Рефакторинг Этот неуклюжий (на русском языке) термин обозначает одну из ветвей замечательного механизма IntelliSense, который значительно продлевает жизнь программиста. Покажем, как он работает. Внутри созданной (тем или иным способом) функции lang_SelectedIndexChanged, вы можете вызвать существующий метод Show, но любовь к искусству требует оформить реакцию несколько иначе. Выделите весь код внутри метода Show и вызовите контекстное меню над выделенным текстом, Выберите команду Refactor→Extract Method, задайте имя нового метода: Respond и нажмите OK. Вызовите Respond(); в теле функции lang_SelectedIndexChanged. Ниже приведен код (только блока <script>), который получился в результате описанных манипуляций. <script language="C#" runat="server"> void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string[] langs = { "C++", "Java", "Assembler", "C#", "PL/I", "Perl" }; foreach (string s in langs) lang.Items.Add(new ListItem(s)); name.Text = "Joe Doe"; lang.SelectedIndex = 0; lang.AutoPostBack = true; } } void Show(object sender, EventArgs e) { Respond(); } void lang_SelectedIndexChanged(object sender, EventArgs e) { Respond(); } void Respond() { if (string.IsNullOrEmpty(name.Text) || lang.SelectedItem == null) msg.Text = "Please, fill in the form"; else msg.Text = name.Text + " selected " + lang.SelectedItem.Text; } </script> Атрибут AutoPostBack сообщает ядру ASP.NET, что каждый раз, когда пользователь выбирает в списке новую строку, элемент провоцирует посылку данных формы на сервер. Проверьте результат. Затем уберите кнопку OK и вновь проверьте результат. Ответьте на вопросы. Какова роль кнопки OK в этом сценарии? На сервере она превратится в <input type="submit">. Влияет ли свойство AutoPostBack на эффективность (производительность) работы страницы? Где произошло событие SelectedIndexChanged? Где оно выявлено? Где обработано? Ответы В этом сценарии кнопка submit нужна для передачи данных при изменении поля name. При изменении строки в выпадающем списке asp:ListBox, форма автоматически выполняет отсыл данных на сервер, багодаря включенному флагу AutoPostBack. Если на страницу поместить несколько выпадающих списков с атрибутом AutoPostBack="true", то ее производительность пострадает. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Событие SelectedIndexChanged выявляется на стороне клиента. Информация о нем передается на сервер с помощью скрытых переменных __EVENTTARGET, __EVENTARGUMENT и функции JScript __doPostBack. Вы можете увидеть эти новые сущности, дав (в браузере) команду View→Source. 1.13. Привязка к данным (Data Binding) В предыдущем примере выбор пользователя постоянно отслеживается и отображается в текстовой строке. Таким образом, можно сказать, что два элемента управления (список asp:ListBox с идентификатором lang и метка asp:Label с идентификатором msg) синхронизированы друг с другом, так как мы изменяем свойство Text одного элемента при изменении свойства SelectedIndex другого. Синхронизация выполнена в обычном для платформы .NET стиле — путем введения реакции на событие SelectedIndexChanged. В ASP.NET существует другой способ решения задачи синхронизации элементов. Он использует метод DataBind класса Control и пару тегов (binding tags) вида: <%# %>. Не путайте эти теги с тегами <% %>, которые обрамляют блок серверного скрипта. Метод DataBind создает постоянную связь между источником данных и каким-либо свойством серверного элемента управления. Вызов этого метода для какого-либо элемента автоматически запускает цепь вызовов методов DataBind для всех элементов, вложенных в данный. Так, вызов Page.DataBind(); или просто DataBind(); заставляет обновить все связи, существующие между дочерними элементами страницы и их источниками данных (DataSource). Для демонстрации механизма привязки к данным создайте копию предыдущего файла, назовите ее Intro13.aspx и скорректируйте код, как показано ниже. <%@ Page language="c#" AutoEventWireup="true"%> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Test Data Binding</title> <script type="text/C#" runat="server"> public void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string[] langs = { "C++", "Java", "Assembler", "C#", "PL/I", "Perl" }; foreach (string s in langs) lang.Items.Add(new ListItem(s)); name.Text = "Peter Norton"; lang.SelectedIndex = 1; } msgName.DataBind(); msg.DataBind(); } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="myForm" runat="server"> <h3>Query Form</h3> <asp:Label Text="Name: " runat="server" AccessKey="N" AssociatedControlID="name"/> <asp:TextBox ID="name" runat="server" AutoPostBack="true"/><br/><br/> <asp:Label Text="Language: " runat="server" AccessKey="L" AssociatedControlID="lang"/> <asp:ListBox ID="lang" runat="server" rows="1" AutoPostBack="true" /> <asp:Button ID="bOk" text="OK" runat="server" /><br /><br /> <asp:Label id="msgName" runat="server" Text="<%# name.Text %>" /> &nbsp;&nbsp;selected:&nbsp;&nbsp; <asp:Label id="msg" runat="server" Text="<%# lang.SelectedItem.Text %>" /><br/> </form></body></html> Здесь мы вновь подключили множество каскадных стилей из файла StyleSheet.css. Этот файл был создан ранее, при разработке кода, работающего с базой данных Northwind. Проверьте результат работы страницы и обратите внимание на следующие моменты. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Отсыл данных на сервер (postback) при выборе пользователем строки списка осуществляется благодаря установке атрибута AutoPostBack="true". Атрибут ListBox OnSelectedIndexChanged и функция lang_SelectedIndexChanged класса Page теперь отсутствуют. В предыдущем документе они были необходимы. Блоки вида <%# %> реализуют механизм привязки к данным. Блок <%# lang.SelectedItem.Text %>, совместно с оператором msg.DataBind(); привязывает свойство Text метки msg к свойству SelectedItem.Text списка lang. Вызов метода DataBind в методе Page_Load необходим для включения механизма DataBinding при каждой загрузке страницы. Значимость кнопки типа submit стала ниже. Добавьте атрибут AutoPostBack="true" в TextBox и сформулируйте изменения в поведении страницы. Какое событие TextBox провоцирует отсыл данных (postback)? (Ответ: TextChanged). Cross-Page Posting Возможность передавать данные из одной страницы в другую значительно упрощается в рамках платформы .NET Framework 2.0 и выше. Рассмотрим один из возможных способов передачи данных. Он использует атрибут PostBackUrl серверной кнопки и свойство PreviousPage класса Page. Предположим, что вы осуществляете переход от страницы P1 к странице P2. Если в P1 есть кнопка с атрибутом PostBackUrl="P2", то в классе Page, поддерживающем страницу P2, существует (не равно null) свойство PreviousPage. Оно позволяет добыть ссылку на предыдущую страницу P1. Имея ссылку на P1, мы можем получить данные любого элемента, расположенного в ней, и использовать их в рамках страницы P2. Метод FindControl класса Page позволяет добыть ссылку на произвольный элемент управления страницы. Создайте документ Intro14.aspx и введите в него следующий код. <%@ Page language="c#" AutoEventWireup="true"%> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Test Data Binding</title> <script type="text/C#" runat="server"> public void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string[] langs = { "C++", "Java", "Assembler", "C#", "PL/I", "Perl" }; foreach (string s in langs) lang.Items.Add(new ListItem(s)); name.Text = "Joe Doe"; } } public void bCheck_Click(object sender, EventArgs e) { if (name.Text == "") msg.Text = "Please, Fill in the form!"; else msg.Text = name.Text + " selected " + lang.Text; } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <form id="myForm" runat="server"> <h3>Query Form</h3> <asp:Label Text="Name: " runat="server" AccessKey="N" AssociatedControlID="name"/> <asp:TextBox ID="name" runat="server" /><br/><br/> <asp:Label Text="Language: " runat="server" AccessKey="L" AssociatedControlID="lang"/> <asp:ListBox ID="lang" runat="server" rows="1" AutoPostBack="true" /><br/><br/> <asp:Label id="msg" runat="server" /><br/> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <asp:Button ID="bCheck" Text="Check" runat="server" OnClick="bCheck_Click"/> <asp:Button ID="bSubmit" Text="Submit" runat="server" PostBackUrl="~/Intro/Intro15.aspx" /> </form></body></html> Обратите внимание на то, что кнопка Submit имеет атрибут PostBackUrl, а кнопка Check — не имеет. В самой форме атрибут action не задан. Это означает, что по умолчанию страница готова отослать данные самой себе (Postback). Обычная серверная кнопка, например Check, именно это и сделает. Исключение составляют кнопки, которые имеют атрибут PostBackUrl. Они будут отправлять данные той странице, которая указана в этом атрибуте. В нашем случае кнопка Submit отправит данные странице Intro15.aspx. Такой страницы нет. Создайте ее и введите в нее следующий код. <%@ Page language="c#" AutoEventWireup="true"%> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Test Cross-Page Posting</title> <script type="text/C#" runat="server"> public void Page_Load(object sender, EventArgs e) { myDiv.Attributes["style"] = "background-color:#ffffcc;"; if (PreviousPage != null && PreviousPage.IsCrossPagePostBack) { msg.Text = "Data from: &nbsp;&nbsp;&nbsp;<em>" + PreviousPage.ToString() + "</em><br/>" + (PreviousPage.FindControl("name") as TextBox).Text + " selected " + (PreviousPage.FindControl("lang") as ListBox).Text; } else Response.Redirect("Intro14.aspx"); } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body><center> <form id="myForm" runat="server" action="Intro15.aspx"> <div runat="server" id="myDiv"> <h3>Cross-Page Posting</h3> <asp:Label ID="msg" runat="server" /><br /><br /> <asp:HyperLink Text="Back" NavigateUrl="~/Intro/Intro14.aspx" ID="linkBack" runat="server" ImageUrl="~/Images/OpenOver.gif" /> </div> </form> </center></body></html> Логика кода поддержки построена таким образом, что неважно, с какой страницы начать работу, P1 или P2. В любом случае пользователь увидит страницу P1, то есть первую из двух созданных нами. Секрет в том, что при запуске страницы P2 она сразу же передаст управление P1. Это делает код: if (PreviousPage != null && PreviousPage.IsCrossPagePostBack) { . . . } else Response.Redirect("Intro14.aspx"); Так как при начальном запуске свойство PreviousPage не установлено (равно null), то выполняется метод Redirect класса HttpResponse. Он и отправляет нас к странице P1. Если же свойство PreviousPage существует, то универсальный метод FindControl поможет добыть данные из элементов чужой страницы. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.38. Иллюстрация механизма обмена данными с помощью Cross-Page Posting Плата за универсальность — необходимость знать и приводить типы. Знание типов не является преградой (вcпомните, что в .NET каждый объект имеет метод GetType), но идентификаторы знать необходимо. Правда, в каком-то другом, более изощренном сценарии, вы можете прогуляться по всем элементам управления чужой страницы и узнать их идентификаторы. Для подтверждения сказанного просто выведите свойство ClientID одного из элементов чужой формы. Например: PreviousPage.Form.Controls[3].ClientID. В этом примере мы попутно показываем, как управлять атрибутами стиля панели <div> myDiv.Attributes["style"] = "background-color:#ffffcc;"; и создать серверную гиперссылку <asp:HyperLink> в виде изображения. Свойство NavigateUrl этого элемента отправляет пользователя назад, то есть, на страницу P1, а свойство ImageUrl указывает, где взять изображение. Запустите любую из двух страниц и сравните поведение кнопок Check и Submit. Убедитесь, что свойство PreviousPage дает доступ к предыдущей странице, а вызов метода ToString() дает имя класса, поддерживающего PreviousPage. Как видите оно имеет вид: intro_intro14_asp. Другие свойства страницы В нашем случае проверка флага IsCrossPagePostBack не нужна, достаточно было убедиться в том, что PreviousPage != null. Но в принципе существует вариант обращения к странице, когда PreviousPage != null, а IsCrossPagePostBack == false. Это происходит при обращении к новой странице в контексте старой (см. справку по методу Server.Transfer). Мы рассмотрим такой вариант позже, а сейчас сообщим, что есть еще одно свойство класса Page — IsCallback, которое характеризует способ обращения к странице. Свойство IsCallback равно true, если пользователь перешел к странице с помощью команды браузера Назад, или выбрав строку из выпадающего списка истории браузера. Следущая таблица показывает зависимость свойств класса Page от всех возможных способов обращения к странице ASPX. Нашему случаю соответствует ячейка, выделенная цветом. Тип запроса\Свойство IsPostBack PreviousPage IsCrossPagePostBack IsCallback Original request false null error false Postback true null error false Cross-page posting false ссылка на Page true false Server transfer false ссылка на Page false false Callback false null error true Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Более сложный пример привязки к данным Следующий документ иллюстрирует работу механизма привязки к данным на примере более сложного элемента управления asp:Repeater. Такое имя носит специальный список (data-bound list control), позволяющий применить некоторые шаблоны отображения к повторяющимся элементам данных (обычно это строки таблиц баз данных). Задаваемые программистом шаблоны (templates) определяют облик воспроизводимых на странице элементов с одинаковой структурой. Для работы следующего примера вам понадобится файл с данными, которые будут воспроизводиться в элементе asp:Repeater. Найдите в папке с материалами курса XML-файл Customers.xml и перенесите его в папку App_Data нашего проекта. В этом файле хранятся данные, отражающие структуру трех связанных таблиц учебной базы данных NorthWind. Ниже приведен фрагмент этого файла. <CustomerData> <Customers> <CustomerID>ALFKI</CustomerID> <CompanyName>Alfreds Futterkiste</CompanyName> <ContactName>Maria Andersen</ContactName> <ContactTitle>Sales Representative</ContactTitle> <Address>Obere Str. 57</Address> <City>Berlin</City> <PostalCode>12209</PostalCode> <Country>Germany</Country> <Phone>030-0074321</Phone> <Fax>030-0076545</Fax> <Orders> <OrderID>10643</OrderID> <CustomerID>ALFKI</CustomerID> <EmployeeID>6</EmployeeID> <OrderDate>1997-08-25 07:00:00</OrderDate> <RequiredDate>1997-09-22 07:00:00</RequiredDate> <ShippedDate>1997-09-02 07:00:00</ShippedDate> <ShipVia>1</ShipVia> <Freight>29.46</Freight> <ShipName>Alfreds Futterkiste</ShipName> <ShipAddress>Obere Str. 57</ShipAddress> <ShipCity>Berlin</ShipCity> <ShipPostalCode>12209</ShipPostalCode> <ShipCountry>Germany</ShipCountry> <OrderDetails> <OrderID>10643</OrderID> <ProductID>28</ProductID> <UnitPrice>6000</UnitPrice> <Quantity>15</Quantity> <Discount>0.25</Discount> </OrderDetails> . . . Как видите строки данных описывают клиентов, их заказы и детали заказов. Корневой элемент XMLдокумента (CustomerData) должен быть в единственном экзмпляре. Мы намерены использовать только строки таблицы Customers (заказы нам не важны). Работать с файлом XML удобно в специализированном редакторе студии. Он показывает данные в табличном виде, как показано ниже, и устраняет возможность внести ошибки. CustomerID CompanyName ContactName ContactTitle Country Phone ANATR Ana Trujillo Emparedados Ana Trujillo Owner Mexico (5) 555-4729 ANTON Antonio Moreno Taquería Antonio Moreno Owner Mexico (5) 555-3932 BERGS Berglunds snabbkцp Christina Berglund Order Administrator Sweden 0921-12 34 65 HUNGO Hungry Owl All-Night Grocers Patricia McKenna Sales Associate Ireland 2967 542 Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Многие студенты имеют привычку производить редактирование в блокноте. Если вы намерены редактировать в нем XML-документ, то не забудьте про Well-Formedness (см. этот термин в MSDN). Правильно завершайте все теги, закройте все элементы, в том числе и корневой. Создайте файл Intro16.aspx и перенесите в него следующий код. <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <script type="text/C#" runat="server"> public void Page_Load () { if (!Page.IsPostBack) { DataSet ds = new DataSet(); ds.ReadXml(MapPath("Customers.xml")); catalog.DataSource = ds; catalog.DataBind(); } } </script> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Repeater</title> <style type="text/css"> th { text-align:left; background-color:#cccccc; } </style> </head> <body> <form id="myForm" runat="server"> <asp:Repeater id="catalog" runat="server"> <HeaderTemplate> <table border="0" width="100%" style="font-family:Tahoma; font-size:x-small;"> <tr><th>CompanyName</th><th>ContactName</th><th>ContactTitle</th> <th>Country</th><th>Phone</th></tr> </HeaderTemplate> <ItemTemplate> <tr> <td><%#DataBinder.Eval(Container.DataItem, "CompanyName")%></td> <td><%#DataBinder.Eval(Container.DataItem, "ContactName")%></td> <td><%#DataBinder.Eval(Container.DataItem,"ContactTitle")%></td> <td><%#DataBinder.Eval(Container.DataItem,"Country")%></td> <td><%#DataBinder.Eval(Container.DataItem,"Phone")%></td> </tr> </ItemTemplate> <SeparatorTemplate><tr><td colspan="6"><hr></td></tr> </SeparatorTemplate> <FooterTemplate> </table> </FooterTemplate> </asp:Repeater> </form></body></html> Запустив страницу, вы увидите таблицу, котора показана на рис 1.39. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 1.39. Элемент Repeater. Привязка к данным DataSet Обратите внимание на способ реализации механизма привязки к данным. Он использует метод DataBind и пару тегов вида: <%# %>. Метод DataBind создает постоянную связь между источником данных и шаблоном ItemTemplate серверного элемента управления (повторителя типа asp:Repeater). В скрипте на стороне сервера работает объект класса DataSet. Его задача: прочесть данные из файла и разместить их в таблице (коллекция таблиц — объектов класса DataTable — вложена в DataSet). Далее включается механизм привязки к данным. Он синхронизирует визуальный элемент формы Repeater с данными таблицы в памяти. Запустите пример и убедитесь, что он точно воспроизводит заданный шаблон (таблицы). Ответьте на вопросы: Каков тип объекта catalog (внутри метода Page_Load)? Что делает директива Import? (см. MSDN). Что делает метод Eval класса DataBinder? (см. MSDN). 1.14. Код поддержки страницы Несмотря на то, что мы получили некоторый опыт работы с файлами типа aspx и даже пишем скрипт на языке C#, мы еще не вполне пользуемся преимуществами технологии ASP.NET. Мы продолжаем работать в стиле, обычном для технологии ASP, в которой статический текст HTML перемешан со скриптом. Он считается не вполне приемлемым для технологии ASP.NET и может стать серьезной помехой при разработкке более сложных Web-страниц. Технология ASP.NET позволяет разделить сферы деятельности программиста и дизайнера страницы. Программист должен работать с файлом типа cs, то есть разрабатывать код, поддерживающий страницу, а дизайнер должен работать с файлом типа aspx, то есть заботиться об облике страницы. В таком подходе документ aspx ссылается на файл поддержки и сообщает тем самым ядру ASP.NET, что при генерации отклика необходимо учитывать код поддержки (code-behind). Чтобы показать, как это работает, создайте новый файл Intro17.aspx и введите в него код, который заметно отличается от предыдущей версии документа. <%@ Page Language="C#" Src="Intro17.cs" Inherits="MyPage" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Intro17</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body><center> <form action="Intro17.aspx" runat="server" method="get"> <h3>Query Form</h3> <asp:Label Text="Name: " runat="server" AccessKey="N" AssociatedControlID="name"/> <asp:TextBox id="name" runat="server" /><br/><br/> <asp:Label Text="Language: " runat="server" AccessKey="L" AssociatedControlID="lang"/> <asp:ListBox id="lang" AutoPostBack="true" OnSelectedIndexChanged="lang_Changed" runat="server" rows="1" /><br /><br /> <asp:Label id="msg" runat="server" /><br /> <asp:Button ID="bSubmit" Text="Submit" runat="server" /> </form></center> </body></html> Обратите внимание на директиву Page. В ней есть ссылка на источник кода поддержки страницы (файл Intro17.cs), а также на класс MyPage, который должен быть объявлен в этом файле. Следующим шагом надо создать файл с кодом поддержки страницы (его называют code-behind). Сделайте это и введите в него следующий код. using System; using System.Web.UI; Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 using System.Web.UI.WebControls; using System.Collections.Specialized; public class MyPage : Page { public ListBox lang; public TextBox name; public Label msg; public void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { string[] langs = { "C++", "Java", "Assembler", "C#", "PL/I", "Perl" }; foreach (string s in langs) lang.Items.Add(new ListItem(s)); name.Text = "Peter Norton"; name.BackColor = System.Drawing.Color.LightGreen; lang.SelectedIndex = 2; Respond(); } } public void lang_Changed (object sender, EventArgs e) { Respond(); } void Respond() { if (string.IsNullOrEmpty(name.Text) || lang.SelectedItem == null) msg.Text = "Please, fill in the form fields"; else msg.Text = name.Text + " selected: " + lang.SelectedItem.Text; } } Этот файл и его содержимое по логике построения и технике исполнения уже мало отличается от традиционного кода, поддерживающего обычное оконное приложение. Мы видим, что: Web-страницей управляет класс MyPage, производный от класса System.Web.UI.Page. В классе объявлены переменные — объекты классов, поддерживающих элементы управления на Webстранице. Заметьте, что многие из них имеют аналоги, живущие в пространстве имен System.Windows.Forms. В классе присутствует обычный метод Respond, а также необычные методы, то есть, методы, реагирующие на события: Page_Load и lang_Changed. Мы не видим явной привязки метода lang_Changed к событию SelectedIndexChanged. Такая привязка всегда присутствует в приложениях типа Windows.Forms и она обычно имеет вид. lang.SelectedIndexChanged += new EventHandler (lang_Changed); Код, связывающий событие с его делегатом, инициализированным адресом функции обработки, на самом деле генерируется в недрах ASP.NET и мы говорили об этом ранее. В коде разметки есть указание на метод обработки события. Вот оно: OnSelectedIndexChanged="lang_Changed". Можно также догадаться, что ядро ASP.NET находит файл с кодом поддержки, компилирует его и использует полученную dll для обработки событий. Важно, чтобы файл с кодом поддержки был доступен ядру ASP.NET (находился в той же виртуальной, или обычной директории, что и файл с описанием самой страницы). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 При запуске приложения со стартовым документом Intro17.aspx выполняется цепь довольно сложных преобразований. Ее результатом является HTML-текст, отправляемый в клиентский браузер. Вы можете просмотреть этот текст обычным способом. Настоящий этап развития приложения является переходным. Вместо однофайловой архитектуры мы впервые используем двухфайловую. Отныне, работая с проектами, мы часто будем пользоваться услугами двухфайловой архитектуры, потому что она заложена во все типовые шаблоны Web-проектов студии. Обратите внимание на отличия нашей переходной модели от настоящей, двухфайловой. Для этого сравните директиву <%@ Page нашего документа с директивой <%@ Page документа TestDB.aspx. Директива Page переходной модели имеет вид. <%@ Page Language="C#" Src="Intro17.cs" Inherits="MyPage" %> Директива Page обычной двухфайловой модели имеет несколько иной вид. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestDB.aspx.cs" Inherits="DBPages_TestDB" %> Более важным отличием обычной двухфайловой модели от переходной является то, что при использовании обычной модели не надо объявлять переменные, соответствующие серверным элементам страницы (то есть, тем элементам, которые объявлены в коде разметки). Объявление этих элементов спрятано в коде за сценой, о котором мы говорили ранее. В переходной модели (с атрибутом Src в директиве <%@ Page) переменные декларированы явно, так как показано в последнем примере Intro17.cs. Отображаем коллекцию QueryString Просмотрите код разметки страницы, обратите внимание на значение атрибута method="get", и вспомните, что оно означает (меняет метод посылки данных формы). Рис. 1.40. Отображение данных коллекции NameValueCollection Чтобы заглянуть во внутреннюю кухню работы ядра ASP.NET, введите изменения в файл Intro17.cs. В метод Page_Load добавьте ветвь условного оператора: else { Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 NameValueCollection pairs = Request.QueryString; string[] keys = pairs.AllKeys; Response.Write( "<link href='../StyleSheet.css' rel='stylesheet' type='text/css' />" + "<table class='myTD'><tr><th>Key</th><th>Value</th></tr>"); foreach (string key in keys) { Response.Write("<tr><td>" + Server.HtmlEncode(key) + "</td>"); string[] vals = pairs.GetValues(key); foreach (string val in vals) Response.Write("<td>" + Server.HtmlEncode(val) + "</td>"); Response.Write("</tr>"); } Response.Write("</table>"); } Вам придется добавить (или позволить сделать это студии) следующую директиву. using System.Collections.Specialized; Без нее не пройдет компиляция кода, в котором используется специализированная коллекция NameValueCollection. Ранее мы говорили, что эта коллекция хранит пары типа (string, string[]). Свойство AllKeys класса NameValueCollection, как нетрудно догадаться, возвращает массив ключей (первых элементов всех пар коллекции). Статический метод Write класса HttpResponse, добываемого с помощью свойства Response, позволяет сгенерировать HTML-строку. Эта строка возвращается клиенту с помощью кодированного потока (HTTP stream). Как видите, для коррекции стиля тела документа, заголовка и ячеек таблицы, а затем для формирования содержимого ячеек таблицы, мы пользуемся методом Write. Сохраните изменения в файлах, запустите страницу на выполнение, а затем сделайте выбор в списке языков. Вы должны увидеть таблицу отображающую пары коллекции QueryString. С помощью этих текстовых строк данные формы отправляются на сервер. Первые две пары строк несут информацию о событии SelectedIndexChanged. Оно, как мы отметили ранее, выявлено на стороне клиента и будет обработано на сервере. Три пары строк вы без труда расшифруете сами. Элемент __VIEWSTATE был сгенерирован на сервере с помощью объекта класса System.Web.UI.StateBag, инкапсулирующего функциональность главного хранилища данных страницы. Вы помните, что __VIEWSTATE — это спрятанный элемент управления (hidden control), который хранит состояния всех элементов формы. Как видите, секретный механизм передачи данных и отложенной обработки событий имеет довольно сложную структуру. В скрытой переменной хранятся и передаются как числовые, так и символьные значения всех серверных элементов. Например, мы видим все строки текста, заполняющие выпадающий список, значение метки msg, и другие поля данных. 1.15. Первоначальные выводы В основу архитектуры ASP.NET (как и всей платформы .NET) положен принцип компонентности. Приложения ASP.NET представляют собой смесь разных компонентов и строится из отдельных самодостаточных единиц. Каждая Web-страница является программируемым объектом, подверженным процедуре just-in-time компиляции и динамического кэширования. Приложение ASP.NET способно отделять клиентскую часть от серверной части. В отличие от ASP, компоненты ASP.NET не нужно предваритетельно регистрировать на сервере. Для переноса приложения с одного сервера на другой достаточно скопировать и перенести только код приложения. Другим способом Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 переноса является использование специальных инструментов, входящих в состав FrontPage Server Extensions. Устанавливая студию, вы установили и эти расширения. В рамках технологии ASP для задания скрипта вы можете использовать два языка: VBScript и JScript, которые являются интерпретируемыми. В рамках технологии ASP.NET скрипт на стороне сервера задается только компилируемыми языками. Поэтому VBScript более не используется, в JScript.Net добавлены типы данных, которых ранее не было, появилась возможность описывать скрипт с помощью языков C#, J#, Perl, COBOL и других. Важным отличием технологии ASP.NET от технологии ASP является то, что настройки приложения не надо хранить в реестре. Компоненты придется регистрировать только в случае использования объектов COM. Если их нет, то достаточно скопировать на сервер некоторые файлы. При размещении страницы на сервере (эта фаза обозначается термином deployment) используются файлы конфигурации. Для общих настроек ASP.NET-приложений, так называемых, установок IIS (Internet Information Services), используется файл machine.config. Просмотрите этот устрашающий файл, он расположен по адресу: %WinDir%\Microsoft.NET\Framework\%FrameworkVersion%\CONFIG Для индивидуальных настроек конкретного приложения используется файл web.config. Оба эти файла имеют формат XML-документов, они хранят мета-данные, управляющие обликом страницы в окне браузера. Элементы управления на стороне сервера разделяют на несколько логических групп: HTML Server Controls — являются серверными эквивалентами обычных элементов HTML. Web Form Controls — довольно точно отображают поведение элементов HTML, но имеют значительно большую функциональность. List Controls — соответствуют группам элементов HTML и дают функциональность электронных таблиц. Rich Controls — инкапсулируют развитый интерфейс и довольно сложную функциональность. Они производят как чистый HTML-код, так и скрипт. Validation Controls — обычно не имеют видимых (оконных) элементов, но дают возможность реагировать на ошибки заполнения форм с помощью кода как на стороне клиента, так и на стороне сервера. Mobile Controls — в зависимости от типа браузера производят либо обычный код HTML, либо код WML. Для того, чтобы превратить обычный элемент HTML в HTML Server Control достаточно добавить в его объявление атрибут runat="server". Этот атрибут сообщает подсистеме ASP.NET, что элемент является объектом соответствующего класса, имеющего свойства и методы. Элементы управления типа Web Form Controls способны возбуждать события (raise events), на которые реагирует браузер (то есть клиент). В отличие от обычных элементов, эти события упаковываются в сообщение и передаются серверу, используя метод post. Поэтому принято говорить, что сервер также реагирует на эти события. Функции обработки событий, конечно-же, расположены на сервере (о чем и напоминает атрибут runat="server"). События обрабатываются с помощью делегатов, как принято во всех других типах приложений на платформе .NET. Стандартного представителя делегатной модели вы недавно видели в лице функции OK_Click. Его стандартность состоит в наличии двух параметров (object sender, EventArgs e). Многие события, такие, например, как MouseOver, не могут быть обработаны на сервере. Они либо игнорируются, либо обрабатываются на стороне клиента, так их обработка на сервере обошлась бы слишком дорого. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 2. Двухфайловая модель разработки приложений ASP.NET 2.1. Работа в режиме дизайна До сих пор для разработки Web-приложений мы в основном пользовались услугами однофайловой модели, так как хотели пощупать руками все детали довольно сложного процесса преобразования входной информации в выходную. Теперь мы намерены использовать мощные инструменты студии для того, чтобы автоматизировать выполнение типовых операций и ускорить процесс разработки Web-приложений. Наша жизнь сильно упростится, но придется иметь дело с кодом, который генерирует студия. Большей частью — это код, генерируемый дизайнером Web-интерфейса. В рамках студии откройте уже существующий сайт MySite. Вы, наверное, заметили, что исследуемый нами тип проекта физически расположен в двух разных местах файловой системы. Основные файлы (с которыми мы работаем) расположены в каталоге проекта. Если вы настроили IIS и работаете под его управлением, то каталог является виртуальным, если вы работаете с сервером Cassini, то это—произвольный каталог файловой системы. Вспомогательные файлы (MySite.sln и MySite.suo) студия по умолчанию предлагает поместить в папке: "My Documents/Visual Studio Projects", но вы можете это изменить и сохранить служебные файлы в произвольном месте (например, вместе с основными файлами). Продолжим развите проекта. Сейчас мы намерены создать страницу, функциональность которой сходна с той, что имеется в предыдущей странице (Intro17). Но теперь попытайтесь добиться этого, работая исключительно в режиме дизайна. Добавьте в проект папку с именем Misc. Добавьте в эту папку новый документ типа Web Form и согласитесь с предложенным именем Default.aspx. Проследите за тем, чтобы был установлен флажок Place code in separate file, который включает двухфайловую модель разработки Web-форм. Сделайте Default.aspx стартовой страницей и переведите редактор студии в режим дизайна. Возьмите на инструментальной панели Toolbox (открытой на вкладке Standard) и положите на форму элементы управления: три метки типа Label (с идентификаторами Label1, Label2 и msg и текстом Name:, Language:, пусто), поле редактирования типа Textbox с идентификатором name, список типа Listbox с идентификатором lang и свойством Row=1 и кнопку с идентификатором bSubmit и текстом Submit. Используйте окно свойств и те же приемы, что и при работе в редакторе Word. Особое внимание обращайте на свойства, имеющие установки по умолчанию. Например, AutoPostBack и другие. Так как элементы формы по умолчанию позиционируются автоматически, они перетекают (flow layout), то мы не можем размещать их в произвольных позициях. В этом случае для выравнивания элементов удобно пользоваться свойством Width. Задайте одинаковые значения этих свойств для меток Name и Language и вы добъетесь выравнивания. Если вы все-таки хотите задавать абсолютные позиции элементов (их координаты в пикселах), то нужно переключить форму в режим абсолютного позиционирования. Для этого с помощью тег-навигатора выделите форму, перейдите в окно свойств и найдите свойство Style. Нажмите кнопку (...), которая появляется в правой колонке. При этом откроется диалог Modify Style. Выделите категорию Position, в списке position выберите тип позиционирования absolute. То же самое можно проделать с любым элементом контейнерного типа (div, span, panel). Выделите список, нажмите кнопку Events в заголовке окна Properties и выполните двойной щелчок над событием SelectedIndexChanged. В тело обработчика введите вызов метода Respond();. При этом появится подсказка. Используйте ее для создания тела метода Respond. Код метода Respond возьмите из файла Intro17.cs. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 В заготовку обработчика события загрузки страницы (Page_Load) введите код, который был в Intro17.cs. Запустите приложение (Ctrl+F5) и убедитесь, что страница работает почти так же, как и ранее. Сформулируйте различия в поведении документа по сравнению с примером Intro17. Попытайтесь устранить их. Обратите внимание на изменение облика документа, которое происходит после нажатия кнопки Submit. Подсказки. Проверьте (в окне Properties) значение атрибута AutoPostBack для ListBox. Вспомните, что оно должно быть равно true. Коллекция QueryString работает только в случае, когда метод отсыла данных на сервер установлен в "get" (по умолчанию method="post"). Поэтому, для успеха в выводе скрытых данных формы следует либо изменить метод передачи данных (установить в get), либо заменить имя коллекции (вместо Request.QueryString задать Request.Form). Изменение облика страницы объясняется тем, что при повторной загрузке подгружается таблица каскадных стилей, которую мы ранее поместили в файл StyleSheet.css. Это делает строка кода: Response.Write("<link href='../StyleSheet.css' rel='stylesheet' type='text/css' />"); Анализ файла описания Web-формы Откройте файл Default.aspx. Обратите внимание на директиву Page. Ссылка на файл кода поддержки страницы является значением атрибута CodeFile, а не Src, как было в примере Intro17. Атрибут Src студией не поддерживается. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Misc_Default" %> Атрибут CodeFile по сути делает то же самое, что и атрибут Src, но он работает только в проекте, созданном в рамках студии. Его цель — сообщить дизайнеру формы, где находится код поддержки. Атрибут Inherits, как и ранее, идентифицирует класс (Misc_Default, производный от Page). Убедитесь в этом, просмотрев код поддержки страницы Default.aspx.cs. Для этого выберите в контекстном меню команду View→Code. Атрибут AutoEventWireup установлен в true. По умолчанию он имеет такое же значение. Эта установка означает неявное связывание событий с методами их обработки. При этом мы не можем изменить стандартное имя обработчика (например, Page_Load). Каркас приложения (page framework) вызывает методы обработки событий автоматически. Если AutoEventWireup установить в false, то мы сможем заменить Page_Load на MyPage_Load, или Misc_Default_Load, но при этом делегаты придется создавать явно. Например: Load += new EventHandler(Misc_Default_Load);. Элемент DOCTYPE HTML-документа указывает, что тип документа соответствует стандарту xhtml1transitional.dtd, определенному международной группой W3C (World Wide Web Consortium). Вы уже убедились, что рассматриваемый каркас работает, благодаря тому, что ядро ASP.NET, как и ранее, производит некую работу, сплавляя код разметки и код на языке C# в единую dll, способную создать корректный выходной HTML-документ. 2.2. Преобразование HTML-страниц в Web-формы В этом разделе мы покажем, как создать обычную HTML-страницу, подключить ее к проекту, а затем преобразовать ее в документ ASP.NET (Web-форму). Такая техника позволяет повторно использовать существующие страницы в новых проектах, преобразуя их в Web-формы. Преобразование HTML-документа в документ ASPX сводится к простой смене расширения файла, вместо htm надо задать aspx. При этом студия выдает обычное предупреждение о том, что смена расширения может привести к потере функциональности файла. Подтвердив желание изменить расширение, вы не получите новый файл с кодом класса, производного от класса Page. Его придется создать вручную. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Итак, используя команду Add New Item, добавьте в папку HTML текущего проекта новую страницу типа HTML Page. Назовите новый документ Zoom.htm, рассмотрите его заготовку и введите в нее следующий код: <!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><title>Zoom Client</title> <script type="text/jscript"> var bZoom = true; function Reset(){ bZoom = true; } function Zoom() { if (bZoom) document.getElementById("myTable").className = "zoom"; } function SetValue () { document.getElementById("lang").value = event.srcElement.id; document.getElementById("myTable").className = ""; bZoom = false; } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <h1>Select the language</h1><p> <input style="border: none;" type="text" size="8" value="Language: " /> <input id="lang" style="border:none;color:red;" type="text" size="10" /> LCID - Locale ID</p> <table id="myTable" onmouseenter="Zoom()" onmouseleave="Reset()" cellpadding="5"> <tr><th>Language:</th><th>LCID</th><th>Encoding:</th></tr> <tr><td><a id="Danish" onclick="SetValue()">Danish (Denmark)</a></td> <td>1030</td><td>ISO-8859-1</td></tr> <tr><td><a id="German" onclick="SetValue()">German (Germany)</a></td> <td>1031</td><td>ISO-8859-1</td></tr> <tr><td><a id="Greek" onclick="SetValue()">Greek (Greece)</a></td> <td>1032</td><td>Windows-1253</td></tr> <tr><td><a id="English" onclick="SetValue()">English (USA)</a></td> <td>1033</td><td>ISO-8859-1</td></tr> <tr><td><a id="Spanish" onclick="SetValue()">Spanish (Traditional)</a></td> <td>1034</td><td>ISO-8859-1</td></tr> <tr><td><a id="Finish" onclick="SetValue()">Finish (Finland)</a></td> <td>1035</td><td>ISO-8859-1</td></tr> <tr><td><a id="French" onclick="SetValue()">French (France)</a></td> <td>1036</td><td>ISO-8859-1</td></tr> <tr><td><a id="Italian" onclick="SetValue()">Italian (Italy)</a></td> <td>1040</td><td>ISO-8859-1</td></tr> <tr><td><a id="Dutch" onclick="SetValue()">Dutch (Netherland)</a></td> <td>1043</td><td>ISO-8859-1</td></tr> <tr><td><a id="Russian" onclick="SetValue()">Russian (Russia)</a></td> <td>1049</td><td>Windows-1251</td></tr> </table> </body></html> Проверьте функционирование новой страницы обычным образом, указав ее адрес в Internet Explorer. Вы должны увидеть таблицу, которая показана на рис.2.1. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 2.1. Результат преобразования HTML-страницы в Web-форму Клиентский скрипт (JScript) демонстрирует технику управления атрибутами таблицы. Функция Zoom вызывается в момент вхождения курсора в пределы таблицы (см. атрибут onmouseenter). Она включает именованный стиль .zoom, заданный в файле StyleSheet.css. При этом размер шрифта таблицы myTable увеличивается на 20%. Стиль .zoom выключается при выборе пользователем одного из языков (строк таблицы). SetValue устанавливает значение текстового поля lang и возвращает стиль таблицы в значение, действующее по умолчанию. Функция Reset, которая вызывается в момент выхода курсора из таблицы (событие onmouseleave), вновь взводит курок, устанавливая флаг bZoom. В первом столбце таблицы размещены гиперссылки, которые используются нестандартным образом. Обратите внимание на отсутствие самой ссылки — атрибута href. Вместо нее используется событие onclick. Реакция на него (в функции SetValue) позволяет выбрать одну из текстовых строк, заданных в виде индексов гиперссылок. Итак, мы имеем обычный HTML-документ Zoom.htm, который используется для выбора текстовой строки и по ходу дела пользуется мышиными (скоростными) событиями для управления атрибутами таблицы. Выполните следующие действия. Перенесите копию документа Zoom.htm в папку Misc и преобразуйте ее в Web-форму простым переименованием файла (Zoom.aspx). Это можно сделать, перетащив файл из одной папки проекта в другую (удерживая клавишу Ctrl). Для переименования файла используйте клавишу F2. Проверьте работу преобразованной страницы Zoom.aspx, дав команду View in Browser. Ответьте на вопросы: Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Какого рода элементом является таблица с индексом myTable? Возможные ответы: обычным элементом HTML, элементом типа HTML control, работающим на стороне сервера, элементом типа Web control. Назовите объекты, ссылки на которые приведены в выпадающем списке окна Properties документа Zoom.aspx, открытого в режиме дизайна? Что изменится, если в объявление таблицы <table> добавить атрибут runat="server"? Существует ли Web-форма (элемент типа <form>) в описании страницы Zoom.aspx? Запоминает ли она значения своих элементов? Для проверки нажмите на панели IE кнопку Обновить (или F5). Существует ли возможность изменять содержимое таблицы с помощью кода (на стороне сервера)? Можно ли использовать язык C# для написания скрипта, работающего на стороне клиента? Так как ответы на 4 последних вопроса отрицательны, то польза от преобразованных страниц (без преобразования самих элементов) невелика. Однако теперь мы можем развивать преобразованную страницу, добавляя на нее новые Web Controls из Toolbox. Они автоматически получают атрибут runat="server" и будут работать, запоминая состояния. Старые, уже существующие элементы, автоматически не преобразовываются в элементы типа Web controls. Их можно либо преобразовать вручную, либо оставить такими, какие они есть. Далее мы внесем изменения так, чтобы таблицв заполнялась на сервере, а затем в рамках другого документа покажем, как заставить сугубо серверные элементы (Web Controls) реагировать на скоростные события на стороне клиента. По умолчанию они для этого не приспособлены. Например, у серверных элементов нет встроенного события onmousemove, или onmouseover, которые имеются у обычных. Добавляем файл с кодом поддержки Попытайтесь добавить файл с кодом поддержки вручную. Это полезное упражнение, оно помогает лучше понять принципы организации файлов и запомнить атрибуты директивы Page. Создайте файл Zoom.aspx.cs в той же папке Misc, что и Zoom.aspx. Затем перенесите в него логику генерации строк таблицы на стороне сервера, убрав соответствующие теги из файла aspx. Для этого вам придется превратить table из обычного элемента в HTML Server Control и работать с методами класса HtmlTable. Приведу результат усилий подобного рода. В файле Zoom.aspx надо изменить директиву Page и таблицу. Директива должна стать такой: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Zoom.aspx.cs" Inherits="Misc_Zoom"%>. В таблицe добавьте атрибут runat="server", удалите из нее все строки, но оставьте сам тег <table>. В файл с кодом поддержки следует внести более заметные изменения. Для удобства добавим три массива с текстовыми строками, которые будут использованы при заполнении таблицы. В методе Page_Load заполним таблицу строками, генерируя их на основе данных из массивов. Работать нужно с объектами классов HtmlTable, HtmlTableRow и HtmlTableCell. using using using using System; System.Web; System.Web.UI; System.Web.UI.HtmlControls; public partial class Misc_Zoom : Page { //=========== Массивы текстовых строк static string[] langs = { "Danish (Denmark)", "German (Germany)", "Greek (Greece)", "English (USA)", "Spanish (Traditional)", "Finish (Finland)", "French (France)", "Italian (Italy)", "Dutch (Netherland)", "Russian (Russia)" }, lcids = Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { "1030", "1031", "1032", "1033", "1034", "1035", "1036", "1040", "1043", "1049" }, encodings = { "ISO-8859-1", "ISO-8859-1", "Windows-1253", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "ISO-8859-1", "Windows-1251" }; protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //========= Строка заголовка содержит три ячейки HtmlTableRow row = new HtmlTableRow(); HtmlTableCell cell = new HtmlTableCell("th"); cell.Controls.Add(new LiteralControl("Language")); row.Cells.Add(cell); cell = new HtmlTableCell("th"); cell.Controls.Add(new LiteralControl("LCID")); row.Cells.Add(cell); cell = new HtmlTableCell("th"); cell.Controls.Add(new LiteralControl("Encoding")); row.Cells.Add(cell); myTable.Rows.Add(row); //========= Генерация строк таблицы for (int i = 0; i < langs.Length; i++) { row = new HtmlTableRow(); cell = new HtmlTableCell(); HtmlAnchor link = new HtmlAnchor(); string s = langs[i]; link.InnerText = s; link.ID = s.Substring(0, s.IndexOf(' ')); link.Style["color"] = "Blue"; link.Attributes.Add("onclick", "SetValue()"); cell.Controls.Add(link); row.Cells.Add(cell); cell = new HtmlTableCell(); cell.Controls.Add(new LiteralControl(lcids[i])); row.Cells.Add(cell); cell = new HtmlTableCell(); cell.Controls.Add(new LiteralControl(encodings[i])); row.Cells.Add(cell); myTable.Rows.Add(row); } } } } Алгоритм прозрачен: создается строка таблицы (HtmlTableRow), затем ячейка (HtmlTableCell), к ней применяется стиль и она заполняется либо текстом, либо ссылкой на объект класса HtmlAnchor. После этого ячейка вставляется в строку. После заполнения всех ячеек строки, она вставляется в таблицу. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Операции заполнения и вставки означают вставку ссылки на вновь созданный объект в коллекцию вложенных элементов какого-то элемента управления страницы. Коллекция Controls имеется у всех элементов типа WebControl. В нашем случае создается либо текст, либо гиперссылка. Класс LiteralControl инкапсулирует функциональность всех текстовых элементов HTML, которые не имеют атрибута runat = "server", то есть элементов, не поддерживающих память состояний (view state). Следующий код позволяет добавить в серверный элемент HtmlAnchor реакцию на событие click. link.Attributes.Add ("onclick", "SetValue()"); Функция JScript SetValue уже есть в файле с кодом разметки. Важен факт, что все строки таблицы (включая заголовок) генерируются в коде на языке C#, работающем на сервере. Запустите страницу на выполнение. Все должно пройти гладко, но если вдруг появятся сообщения о странных ошибках, то удалите всю папку mysite, которая расположена во временном хранилище ASP.NET. Не спутайте эту папку с папкой MySite вашего сайта. Дело в том, что описанный способ радикального, и ручного преобразования архитектуры страницы ведет себя достаточно капризно. Думаю, что мешает уже существующий, автоматически сгенерированный студией, код. В таких случаях помогает удаление этого кода. Будьте осторожны и не удалите свой код. Вопросы Каков тип объекта myTable? Где он объявлен? Открыв документацию MSDN, cравните свойства классов HtmlAnchor и HyperLink. Объясните различия. Например, почему класс HtmlAnchor не имеет свойства Text, а класс HyperLink имеет? Подготовку массивов по логике вещей хочется перенести из функции OnInit в конструктор класса Misc_Zoom. Попытайтесь это сделать. Заметьте, что в начальной заготовке файла типа Web Form конструктор отсутствует. Этим проект ASP.NET-приложения отличается от проекта Windows Form. 2.3. Управление серверными элементами (Web Controls + JScript) В документе Zoom.aspx мы показали, как превратить обычные HTML-элементы в их серверные аналоги — HTML Server Controls. Для этого достаточно добавить атрибут runat="server". Поставим перед собой задачу: создать Web-форму, аналогичную данной (Zoom.aspx), но на основе сугубо серверных элементов типа Web Server Controls. При этом будем использовать обычный для технологии ASP.NET способ разработки страницы: работа в режиме дизайна и выбор элементов с вкладки Standard (а не HTML, как было при разработке страницы Zoom.aspx). Мы хотим добиться точно такого же (или сходного) поведения страницы, которое было ранее. Цель этого опыта: убедиться, что серверные элементы Web Server Controls ни в чем не уступают элементам HTML Server Controls, то есть, могут реагировать на события как на стороне сервера (отложенная обработка событий), так и на стороне клиента (скоростные события). Для этого придется приложить некоторые усилия, так как по умолчанию серверные элементы Web Server Controls не обладают скоростными событиями (не желают на них реагировать). Решение состоит в том, что клиентский браузер не видит серверных элементов. Вы помните, что на определенном этапе ядро ASP.NET превращает их в обычные. Именно в этот момент мы и должны добавить нужную функциональность. Следующий пример показывает, как это сделать. Добавьте в папку Misc новую страницу (Web Form) с именем ZoomASPX.aspx. В режиме дизайна скопируйте в нее все элементы формы из предыдущей страницы Zoom. Для этого: выделите все (Ctrl+A), скопируйте в буфер обмена (Ctrl+C), перейдите на страницу ZoomASPX, опять выделите все (Ctrl+A), и восстановите содержимое буфера (Ctrl+V). Удалите элемент <table id="myTable"> и замените его серверным элементом Table с тем же значением id="myTable". Будьте внимательны тег должен быть не <table>, а <asp:Table>. Удалите элемент <input id="lang"> и замените его серверным элементом Label с тем же id="lang". Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Вы работаете в режиме WYSIWYG (What You See Is What You Get) и сразу видите конечный результат установки свойств элементов. Ниже приведен код, который мне удалось получить в режиме дизайна. Здесь, как и ранее, таблица пуста, мы намерены генерировать ее строки программным способом. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="ZoomASP.aspx.cs" Inherits="Misc_ZoomASP" %> <!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>ZoomASP Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body> <form runat="server" id="myForm"> <h1>Select the language</h1> &nbsp;Language:&nbsp;&nbsp; <asp:Label ID="lang" runat="server" Width="80px" ForeColor="Red"/> &nbsp;LCID - Locale ID<br /><br /> <asp:Table ID="myTable" runat="server" /><br /> <asp:Button runat="server" Text="Submit" /> </form></body></html> Запустите приложение и убедитесь, что форма отображает элементы управления, но таблица пуста. Обратимся к файлу с кодом поддержки страницы и попытаемся добавить строки таблицы с помощью кода на C#. Очевидно, что это надо делать в теле обработчика Page_Load. Вспомогательные массивы с данными таблицы и другие переменные целесообразно заготовить еще раньше. Отличие нового варианта от старого состоит в том, что работа теперь ведется с другими классами — теми, что поддерживают Web Server Controls. Вместо класса HtmlTableRow мы работаем с классом TableRow. Вместо класса HtmlTableCell используются классы TableCell и TableHeaderCell. Вместо класса HtmlAnchor работает класс HyperLink. Элементу <table> соответствует класс HtmlTable, а элементу <asp:Table> — Table. Преобразованный код класса нашей страницы Misc_ZoomASPX выглядит следующим образом. public partial class Misc_ZoomASP : Page { // Вставьте код заполнения массивов (тот же, что и раньше) // string[] langs = protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //========= Строка заголовка содержит три ячейки TableRow row = new TableRow(); TableHeaderCell h = new TableHeaderCell(); h.Controls.Add (new LiteralControl("Language")); row.Cells.Add(h); h = new TableHeaderCell(); h.Controls.Add (new LiteralControl("LCID")); row.Cells.Add(h); h = new TableHeaderCell(); h.Controls.Add (new LiteralControl("Encoding")); row.Cells.Add(h); myTable.Rows.Add(row); Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 //========= Генерация строк таблицы for (int i = 0; i < langs.Length; i++) { row = new TableRow(); TableCell cell = new TableCell(); cell.ForeColor = Color.Blue; HyperLink link = new HyperLink(); string s = langs[i]; link.Text = s; link.ID = s.Substring(0, s.IndexOf(' ')); cell.Controls.Add(link); row.Cells.Add(cell); cell = new TableCell(); cell.Controls.Add(new LiteralControl(lcids[i])); row.Cells.Add(cell); cell = new TableCell(); cell.Controls.Add (new LiteralControl(encodings[i])); row.Cells.Add(cell); myTable.Rows.Add(row); } lang.Text = "Undefined"; } } } Проверьте работу страницы. Убедитесь, что нам удалось воссоздать облик страницы Zoom, но не ее поведение. Отметим детали. Гиперссылки не работают, потому что мы не знаем как ими управлять (да еще нестандартным образом). Напомню, что это объекты класса HyperLink (из категории Web Controls). Стало очевидно, что управлять таблицей программным способом значительно удобней, чем в коде HTML. Такая техника позволяет гибко реагировать на изменения ситуации и динамически менять содержимое таблицы. За это удобство приходится платить потерей способности запоминать содержимое таблицы и восстанавливать его при повторных загрузках. Убедитесь в этом, нажав кнопку Submit. Вспомните, любая серверная кнопка провоцирует повторный обмен, так как по умолчанию обладает мнемоникой Submit. При ее нажатии данные формы собираются, упаковываются в HTTP-сообщение и отправляются на сервер. Способ упаковки зависит от метода передачи (get или post). Итак, мы убедились, что строки таблицы, сгенерированные нашим кодом, не попадают в скрытую переменную __VIEWSTATE и поэтому не запоминаются. Конечно, можно было бы сразу заполнить всю таблицу в коде разметки, но тогда бы мы потеряли удобство заполнения таблицы программным способом. Для исправления ситуации надо просто вынести часть кода из ветви if (!IsPostBack). Решите эту задачу самостоятельно. Скрипт на стороне клиента Для оживления страницы — осуществления выбора одной из текстовых строк в реакции на нажатие левой кнопки мыши, необходимо ввести скрипт, работающий на стороне клиента. Это можно сделать несколькими способами: Непосредственно ввести блок <script> в модуль описания интерфейса (файл ZoomASPX.aspx) и связать его с событием (атрибутом) onclick каждой гиперссылки. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Создать блок <script> программным способом (в коде поддержки страницы) и также связать его с событием onclick каждой гиперссылки. Общей задачей для этих двух способов, как видите, является привязка скрипта к событию. Так как строки таблицы (и гиперссылки) генерируются кодом, то и эту задачу удобно решить с помощью кода. Вставьте следующую строку. Место вставки вычислите самостоятельно. link.Attributes.Add ("onclick", "SetValue()"); Заметьте, что серверный элемент HyperLink в принципе не имеет события onclick, и эта строка кода не добавляет реакцию на событие onclick в HyperLink. Она только сообщает серверу, что на этапе преобразования страницы (Render) надо добавить реакцию на событие onclick в аналог (или, отображение) элемента HyperLink, которым на самом деле будет элемент <a> (anchor). Вспомните, что обычный элемент <a>, конечно же, умеет реагировать на событие onclick. Теперь рассмотрим, как может выглядеть блок <script>, если вставить его непосредственно в файл с кодом разметки. <script type="text/jscript"> function SetValue () { document.getElementById("lang").innerText = event.srcElement.id; } </script> Отсутствие атрибута runat="server" тега script говорит о том, что он будет выполняться на стороне клиента. Запустите и проверьте работу страницы. Она должна работать почти так же, как и первая версия Zoom.aspx. Обратите внимание на то, что мы работаем со свойством innerText элемента lang, а не свойством value, как было ранее. Почему? Ответ на этот вопрос можно получить, рассмотрев выходной код разметки (результат работы ASP.NETмашины). Оказывается, серверный элемент asp:Label трансформируется ASP.NET в обычный span. Этот элемент не имеет свойства value. Ранее же мы использовали свойство value потому, что имели дело с элементом <input type="text">, а не span. Вывод: для принятия правильных решений надо знать таблицу преобразований ASP.NET-машины (что, и во что она трансформирует). Скоростные события Остались нереализованными лишь две функции, работающие на стороне клиента: Zoom() — увеличение шрифта, и Reset() — взвод курка. Первая должна запускаться в ответ на событие onmouseenter, а вторая в ответ на onmouseleave. Серверные элементы по замыслу Microsoft не должны обладать скоростным поведением, и поэтому по умолчанию они не поддерживают событий типа: onmouseenter или onmouseleave, но мы можем добавить указанные атрибуты в отображения серверных элементов, и тем самым, изменить их поведение. Применив знакомую тактику, получим код, который вам следует вставить внутрь метода Page_Load. Местоположение вставки определите самостоятельно, учитывая, что эти установки однократные. myTable.Attributes.Add ("onmouseenter", "Zoom()"); myTable.Attributes.Add ("onmouseleave", "Reset()"); Одновременно внесите изменения в скрипт на стороне клиента. <script type="text/jscript"> var bZoom = true; function Reset(){ bZoom = true; } function Zoom() { if (bZoom) document.getElementById("myTable").className = "zoom"; } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 function SetValue () { document.getElementById("lang").innerText = event.srcElement.id; document.getElementById("myTable").className = ""; bZoom = false; } </script> Запустите страницу на выполнение и убедитесь, что ее поведение соответствует желаемому. Наш опыт позволяет сделать вывод, что элементы типа Web Controls, не теряя способностей своих аналогов типа HTML Controls, предоставляют новые возможности, главной из которых является разделение функций между модулем описания интерфейса и модулем реализации программной логики (Codebehind). Скрипт порождает скрипт Мы рассмотрели, как ввести в серверный элемент реакцию на событие, обрабатываемое на стороне клиента. Его схема такова: На этапе генерации HTML-аналога серверного элемента (в коде поддержки страницы) вставляем атрибут: control.Attributes.Add ("eventName", "func()"); В файл с кодом разметки страницы (aspx-файл) обычным способом добавляем клиентский скрипт: <script type="text/jscript"> function func() { . . . } </script> Рассмотрим другой способ введения клиентской реакции серверного элемента. Он отличается тем, что клиентский скрипт (как и атрбут) вставляется программным способом. Можно сказать, что клиентский скрипт генерируется в ходе выполнения серверного скрипта (кода поддержки страницы). Основную работу выполняет метод RegisterClientScriptBlock класса Page. Спусковым крючком, как и в первом способе, является элемент типа Web Control. Поэтому первая часть схемы остается неизменной: control.Attributes.Add ("eventName", "func()");. Для демонстрации второго способа закомментируйте весь блок клиентского скрипта, который вы вставили в файл ZoomASPX.aspx, и добавьте внутрь метода Page_Load следующий код: if (!IsClientScriptBlockRegistered("MyScript")) RegisterClientScriptBlock ("MyScript", // Этот скрипт работает у клиента "<script>function SetValue() {document.all.language.value = id;}</script>"); Условный оператор необходим для того, чтобы регистрация была однократной. Строка "MyScript" обеспечивает уникальность скрипта (она выполняет роль ключа ассоциативного массива). Такое решение характерно для .NET Framework 1.1. Оно справедливо и для более высоких версий, но сам подход, использующий класс Page, уже устарел. Поэтому код, приведенный выше, рассматривайте как иллюстрацию идеи использования метода RegisterClientScriptBlock. В .NET 2.0 следует использовать метод RegisterClientScriptBlock класса ClientScriptManager, а не класса Page. Доступ к объекту этого класса обеспечивает свойство ClientScript класса Page. Ниже приведена полная версия скрипта, который генерируется программным способом в ходе обработки страницы на сервере. Вставьте этот фрагмент в число данных класса нашей страницы. static string script = @"<script type='text/jscript'> var bZoom = true; function Zoom() { if (bZoom) document.getElementById("myTable").className = "zoom"; Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 } function SetValue () { document.getElementById("lang").innerText = event.srcElement.id; document.getElementById("myTable").className = ""; bZoom = false; } function Reset() { bZoom = true; } </script>"; В метод Page_Load добавьте фрагмент кода, который регистрирует (а, по сути, вставляет) созданный ранее блок </script>. Type type = GetType(); if (!ClientScript.IsClientScriptBlockRegistered(type, "MyScript")) ClientScript.RegisterClientScriptBlock(type, "MyScript", script); Способ задания строкового литерала (текста скрипта) с помощью оператора @"" вызывает уважение, очень изящно, никаких символов продолжения строки, плюсов, и т.д., чего не скажешь о GetType(). Для чего понадобился тип (Type type)? Документация говорит, что это связано с Security. Когда речь заходит об этом темном искусстве, то я чувствую себя так, будто у меня хотят отнять чистые и ясные (с трудом завоеванные) представления и вместо них ввести нечто туманное и расплывчатое. Тип, оказывается, подтверждает, что клиентский скрипт (зверь, которого надо бояться) произведен именно тем элементом, который воспроизводится браузером, а не привнесен злоумышленником. Нечто вроде военного донесениея: "Заделана еще одна дыра в системе безопасности". Запустите приложение и убедитесь, что мы полностью воспроизвели поведение исходной HTML-страницы Zoom.htm. Основную роль в решении этой задачи играет сгенерированный программным способом скрипт. Именно он включает и выключает заготовленный ранее каскадный стиль zoom. Событие ServerClick В рассматриваемом нами примере таблица содержит три колонки. В ячейки первой колонки вложены элементы HyperLink, относящиеся к группе Web Server Controls. Вы знаете, что мы могли бы заменить их на элементы HtmlAnchor, относящиеся к группе HTML Server Controls. В соответствии с принятым нами сценарием гиперссылки используются нестандартным способом. Они не отсылают нас к какой-либо другой странице сайта, а всего лишь производят выбор текстовой строки (языка). Продолжая рассматривать многочисленные варианты решения этой задачи, отметим, что элемент типа HtmlAnchor имеет событие ServerClick (а элемент типа HyperLink—не имеет). Само название события говорит о том, что оно обрабатывается на стороне сервера, то есть, вызывает цикл обмена, что достаточно дорого (если часто кликать). Чтобы довести до конца начатое исследование, рассмотрим, как организовать реакцию на событие ServerClick в предположении, что в ячейки таблицы внедрены элементы HtmlAnchor, а не HyperLink. Перейдите в окно Solution Explorer и создайте методом перетаскивания файла (Ctrl+Drag&Drop) копию страницы ZoomASP. При этом студия копирует оба файла (aspx и aspx.cs), Назовите копию ZoomServer. Вручную замените имя класса (Misc_ZoomASP на Misc_ZoomServer). Заметьте, что это надо сделать в двух файлах. Замените элемент HtmlAnchor на HyperLink, Создайте анонимного делегата (изящное новшество от .NET 2.0): link.ServerClick += delegate(object lnk, EventArgs args) { lang.Text = ((HtmlAnchor)lnk).ID; }; Замените link.Text на link.InnerText, Сократите скрипт (удалите bZoom, SetValue и Reset, но оставьте Zoom), Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Удалите строки кода, которые добавляют атрибуты onclick и onmouseleave, Запустите страницу на выполнение и проверьте ее в работе. Поведение страницы имеет лишь отдаленное сходство с предудущей версией. Теперь каждое нажатие мыши приводит к обмену данными между сервером и клиентом (round trip), что, несомненно, является недостатком. Ясно, что такие скоростные события, как мышиные клики, по возможности, следует обрабатывать на стороне клиента, поэтому этот вариант решения задачи в меньшей степени соответствует задуманному сценарию. Рассмотренный вариант решения задачи будет вполне оправдан, если в реакции на мышиный клик станет необходимым использование ресурсов сервера, которые недоступны на стороне клиента. Подводя итог, отметим: Несмотря на врожденные ограничения серверных элементов (в обработке скоростных событий), мы убедились, что существуют способы коррекции их поведения. Элементы типа Web Controls могут полностью заменить обычные элементы HTML, даже если необходимо обеспечить оперативное управление динамикой документа на стороне клиента. Универсальность серверных элементов позволяет использовать их в различных сценариях, в том числе и нестандартным способом. Скрипт на стороне сервера, хоть и вносит задержки обмена информацией между клиентом и сервером, но позволяет пользоваться ресурсами сервера. Стандартное решение Возвращаясь к последнему примеру, отметим, что если отвлечься от деталей реализации и оставить только логику поведения страницы (выбор текстовой строки из списка возможных вариантов), то ясно, что ее можно реализовать другими, стандартными средствами. При этом можно оставаться в рамках возможностей элементов типа Web Controls, не расширяя их путем вставки атрибутов в отображения серверных элементов. Например, выбор текстовой строки можно обеспечить с помощью выпадающего списка (элемента asp:DropDownList), работающего на стороне сервера. В качестве упражнения создайте страницу Select.aspx и добейтесь, чтобы она имела вид, показанный на рис. 2.2. Рис. 2.2. Иллюстрация обработки события ServerClick При создании кода разметки страницы вы можете копировать элементы уже существующих страниц и восстанавливайть их в окне редактирования новой страницы. Внутреннюю коллекцию текстовых строк выпадающего списка заполните с помощью кода (то есть, в теле метода Page_Load), введите реакцию на событие SelectedIndexChanged (на стороне сервера), и корректируйте значение поля lang в соответствии с выбором пользователя. Вспомните, что реакция на событие SelectedIndexChanged будет правильно работать только при условии установки флага AutoPostBack. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Второй элемент выбора asp:CheckBox, который вы видите на рис. 2.2, тоже принадлежит семейству Web Controls. Воздействуя на него, пользователь включает или выключает текстовый элемент (input или span) с идентификатором def. Последний выводит сообщение, которое вы видите на экране справа внизу. Попытайтесь реализовать реакцию на событие onclick элемента asp:CheckBox на стороне клиента, а не на стороне сервера. Если вам не удалось решить эту задачу самостоятельно, то рассмотрите решение, приведенное ниже. Вот текст файла с кодом разметки. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Select.aspx.cs" Inherits="Misc_Select" %> <!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> <title>Select Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body onload="ShowDefault();"> <form id="myForm" method="post" runat="server"> <h3>Select the language</h3><br /> <asp:DropDownList id="list" runat="server" Width="168px" AutoPostBack="true" onselectedindexchanged="list_SelectedIndexChanged" /> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; <asp:CheckBox id="chDef" Text="Show Default" runat="server" /> <br /><br /> <span>Language:</span>&nbsp; <asp:Label id="lang" runat="server" ForeColor="Red" /> &nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp; <span id="def" style="color:Red;" /> </form></body></html> Теперь приведем код класса, поддерживающего страницу. Он расположен в файле Select.aspx.cs. public partial class Misc_Select : Page { static string[] langs = { "Danish (Denmark)", "German (Germany)", "Greek (Greece)", "English (USA)", "Spanish (Traditional)", "Finish (Finland)", "French (France)", "Italian (Italy)", "Dutch (Netherland)", "Russian (Russia)" }; protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { foreach (string s in langs) list.Items.Add(s); list.SelectedIndex = 3; lang.Text = list.SelectedItem.Text; chDef.Attributes.Add("onclick", "ShowDefault()"); } Type type = GetType(); if (!ClientScript.IsClientScriptBlockRegistered(type, "MyScript")) ClientScript.RegisterClientScriptBlock(type, "MyScript", @"<script type='text/jscript'> function ShowDefault() { Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 document.getElementById('def').innerText = document.getElementById('chDef').checked ? 'Default Language is English' : ''; } document.onload='ShowDefault()'; </script>"); } protected void list_SelectedIndexChanged(object sender, EventArgs e) { lang.Text = list.SelectedItem.Text; } } Если вы запустите эту страницу под управлением студии 2008, и попытаетесь выбрать строку в списке, то, вероятно, получите сообщение от системы безопасности. Invalid postback or callback argument. Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation. Здесь вполне ясно говорится о проблеме и способах ее решения. Нам рекомендуют: либо выключить флаг EnableEventValidation (добавить атрибут EnableEventValidation= "false" внутрь директивы <%@ Page %>), либо зарегистрировать проверку события с помощью объекта класса ClientScriptManager. и его метода RegisterForEventValidation. Первое решение просто выключает заплату в системе безопасности, о которой мы говорили ранее. Второе решение, видимо, лучше, так как учитывает требования безопасности, предъявляемые к клиентскому скрипту. В документации MSDN приведены решения, основанные на регистрации проверки событий. Они относятся к новым элементам управления, построенным на основе наследования от класса Control. Класс-потомок должен реализовать интерфейс IPostBackEventHandler. Это означает, что он должен иметь метод c сигнатурой void RaisePostBackEvent (string eventArgument). В этом методе генерируется событие, которое требует проверки. Кроме того, класс-потомок должен иметь свою версию виртуального метода Render. Именно в этом методе следует регистрировать проверку события и ето выглядит так. protected override void Render(HtmlTextWriter writer) { ClientScript.RegisterForEventValidation (list.UniqueID, this.ToString()); base.Render(writer); } Очевидно, что второй способ выходит за рамки нашего сценария, поэтому мы остановимся на первом. Добавьте атрибут EnableEventValidation ="false" внутрь директивы <%@ Page %>. Запустите страницу Select.aspx и сделайте выбор языка. Значение метки def должно изменяться мгновенно, так как отсутствует отсыл данных на сервер и возврат обновленной страницы. Событие SelectedIndexChanged выпадающего списка должно быть обработано на сервере. Оно провоцирует обмен данными с сервером, благодаря установке свойства AutoPostBack. Реакция на событие onclick внедряется в отображение серверного элемента asp:CheckBox, которым является <input type="checkbox">. В этом легко убедиться, если просмотреть код, доставляемый клиенту. В нем мы видим, что серверный элемент asp:CheckBox превратился в: <input id="chDef" type="checkbox" name="chDef" onclick="ShowDefault();" /> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Мы обратили ваше внимание на то, что ASP.NET сопротивляется внедрению реакции на событие onclick, причем ASP.NET 2.0 и выше реагирует на внедрение атрибута onclick в asp:CheckBox более болезненно, чем версия 1.1. Система замечает, что клиентский скрипт для серверного элемента внедрен некорректно и вырабатывает исключение при повторных выборах языка. Обходной маневр состоит в установке атрибута EnableEventValidation ="false" директивы Page. Думаю, что тут что-то еще не доработано и нас ждут изменения. Подведем итог. Мы исследовали возможности управления серверными элементами и выяснили, что на сервере (в коде на языке C#) можно создавать как сами элементы управления, так и JScript-код, который обеспечит динамику их поведения на стороне клиента. Разработчики .NET уделяют значительное внимание вопросам безопасности, и пытаются создать средства идентификации скрипта, который приходит вместе с HTML-документом. 2.4. Проверка данных, вводимых пользователем Оперативная проверка данных, которые пользователь вводит в элементы управления на Web-форме, является средством повышения эффективности работы приложения. Инфраструктура ASP.NET предоставляет в наше распоряжение ряд специальных элементов управления из категории Web Controls, которые автоматизируют процесс проверки данных. Эти элементы (валидаторы), можно ассоциировать с любым другим элементом, позволяющим вводить информацию. При компиляции кода, в котором присутствуют валидаторы, генерируется скрипт, работающий на стороне клиента. Он автоматически следит за данными ассоциированых элементов и при нарушении определенных условий выводит текст сообщения в отведенное вами на странице место. Типы валидаторов приведены в следующей таблице. Валидатор Проверяет RequiredFieldValidator Факт заполнения поля ввода CompareValidator Выполнение условия для данных в поле ввода RangeValidator Вхождение в границы диапазона, выполнение двух неравенств RegularExpressionValidator Соответствие данных шаблону (regular expression) CustomValidator Выполнение условия, заданного программистом ValidationSummary Нарушение условия каким-либо другим валидатором Валидатор типа ValidationSummary способен суммировать сообщения об ошибках ввода, обнаруженных другими валидаторами и отображать их различным образом: В виде простого или маркированного списка, В виде параграфа, В виде MessageBox. Выбор из этого списка осуществляется с помощью свойств DisplayMode и ShowMessageBox. С одним и тем же элементом управления можно ассоциировать несколько валидаторов. Валидатор типа RequiredFieldValidator следит за тем, чтобы поле ввода было заполнено, другие типы валидаторов проверяют качество введенной информации. Регистрация пользователей Для приобретения опыта в применении валидаторов зададимся целью создать страницу, которая имеет примерно такой вид: Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 2.3. Проверка данных, вводимых пользователем, с помощью валидаторов Зеленым цветом выделены сообщения валидаторов. Они сгенерированы скриптом, работающим на стороне клиента во время работы с полями формы. На рисунке вы видите один из сценариев ввода, который вызывает реакцию валидаторов на нарушения заданных ими правил. Обычно скрипт срабатывает в момент перехода фокуса из одного поля ввода в другое. Добавьте в папку Misc существующего проекта новую Web-форму с именем Validators.aspx. В режиме дизайна введите текст: Query Form. Выделите его и с помощью панели инструментов Formatting отредактируйте так, чтобы он соответствовал заголовку. Для этого в списке Block Format выберите формат заголовка (Heading4). Внутрь панели div вставьте текст подсказки. Например: <span>Your Name:</span>. Возьмите элемент asp:TextBox (окно редактирования) и положите на форму справа от контейнера span. Например: <asp:TextBox id="tName" runat="server" Text="Alex Black" class="pos110"/>&nbsp; Выравнивать элементы текста можно с помощью именованных стилей. Например, class="pos110". Именованные стили pos110 и pos250 уже присутствуют в таблице стилей StyleSheet.css (см. раздел "Внешний шаблон стилей"). Некоторые установки атрибутов и стилей проще делать в режиме редактирования кода (Source). Валидаторы различных типов берут на панели инструментов (вкладка Validation) и размещают рядом с элементами, данные которых они проверяют. Если одно поле ввода опекают два валидатора (это справедливо для всех полей, кроме поля tName), то целесообразно оба поместить в одну точку (друг над другом). Используйте для этого значение absolute атрибута style.position. Такой трюк проходит потому, что разные типы валидаторов не могут сработать одновременно. Добейтесь того, чтобы текст располагался в три столбца. В первом столбце — текст, во втором — элементы типа asp:TextBox, а в третьем—валидаторы. Это можно сделать без помощи таблиц. Для стилизации панели <div> и позиционирования (выравнивания) валидаторов пользуйтесь стилями. Например, <div class="darkShadow">, или <asp:RequiredFieldValidator CssClass="pos250" />. Настройки валидаторов Рассмотрим особенности настройки атрибутов валидаторов. Проще всего описать валидатор типа RequiredFieldValidator. Ему необходимо знать идентификатор контролируемого им элемента ControlToValidate и текст сообщения ErrorMessage, смысл которого — напоминание о необходимости заполнить определенное поле. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Более сложным является RangeValidator. Он, кроме уже отмеченных атрибутов, требует задать тип данных (Type), в который преобразуется текст проверяемого элемента, и допустимый диапазон изменения (MinimumValue и MaximumValue). Логика нашего приложения (см. рис. 2.3) требует динамически вычислять границы диапазона. Это можно сделать внутри Page_Load, например: ageRangevalidator.MinimumValue = DateTime.Now.AddYears(-55).ToShortDateString(); Чуть более сложным является валидатор типа CompareValidator. Он требует определить величину, с которой будет производиться сравнение (ValueToCompare) и саму операцию сравнения (Operator). Валидатор такого типа умеет сравнивать введенное значение тремя различными способами: Сравнение с константой. Пример. Вводимое значение должно быть целым и положительным. <asp:CompareValidator id="v" runat="server" Type="Integer" ControlToValidate="t" Operator="GreaterThan" ValueToCompare="0" ErrorMessage="Please enter a whole number zero or greater" /> Сравнение с другим полем. Например, хотим, чтобы были идентичны обе попытки ввести пароль в поля t1 и t2. <asp:CompareValidator id="v" runat="server" ControlToValidate="t1" ControlToCompare="t2" ErrorMessage="Passwords do not match!" /> Проверка типа данных. Например, если вводимое значение должно быть в денежном формате, то следующий валидатор пропустит такие значениея: 35,22 –35,22, но не пропустит: 35,222 –35.22 –35,22р 35,22m. <asp:CompareValidator id="v" runat="server" Type="Currency" ControlToValidate="t" Operator="DataTypeCheck" ErrorMessage="Please enter a correct decimal value" /> Наиболее сложным в управлении является валидатор типа RegularExpressionValidator. Он требует знания языка Регулярных Выражений — особого языка описания шаблонов, на соответствие которым проверяются вводимые пользователем данные. Задача несколько упрощается в рамках .NET 2.0 и выше. Вы можете воспользоваться готовыми, наиболее часто используемыми шаблонами. Для этого выделите в режиме дизайна валидатор и найдите его свойство ValidationExpression. Нажмите кнопку (...) и просмотрите в окне диалога список предлагаемых выражений. Другой возможностью является поиск и анализ шаблонов на сайте www.regexlib.com. Упражнение. Создайте валидатор, который проверяет правильность ввода ожидаемой даты приезда. Дата должна быть не позднее 2-х недель, после текущей. Учтите, что текущая дата все время меняется, она зависит от времени обращения клиента к серверу. Следовательно, она должна устанавливаться динамически. Для простоты будем считать, что клиент и сервер находятся в одной временной зоне, то есть серверное и клиентское время одинаковы. Решение: validator.MaximumValue = DateTime.Now.AddDays(14).ToShortDateString(); Ниже приведен код разметки страницы Validators. <%@Page Language="C#" AutoEventWireup="true" CodeFile="Validators.aspx.cs" Inherits="Misc_Validators"%> <!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><title>Validators Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body><form id="form1" runat="server"> <h4>Query Form</h4> <div class="darkShadow"><br /> <span>Your Name:</span> <asp:TextBox id="tName" runat="server" Text="Alex Black" CssClass="pos110"/>&nbsp;&nbsp; Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <asp:RequiredFieldValidator id="nameReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ControlToValidate="tName" ErrorMessage="You must enter your name"/> <br /><br /><span>Birth Date:</span> <asp:TextBox id="tBirth" runat="server" Text="01.01.1980" CssClass="pos110"/>&nbsp;&nbsp; <asp:RequiredFieldValidator id="ageReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ControlToValidate="tBirth" ErrorMessage="This field is reqiured"/> <asp:RangeValidator id="ageRangevalidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" Type="Date" ControlToValidate="tBirth" ErrorMessage="Applicants must be between 23 and 55" /> <br /><br /><span># Articles:</span> <asp:TextBox id="tArt" runat="server" Text="38" CssClass="pos110"/>&nbsp;&nbsp; <asp:RequiredFieldValidator id="artReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ControlToValidate="tArt" ErrorMessage="This field is reqiured"/> <asp:CompareValidator id="articlesCmpValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" Type="Integer" ControlToValidate="tArt" ErrorMessage="Please enter a whole number (zero or greater)" ValueToCompare="0" Operator="GreaterThanEqual"/> <br /><br /><span>E-mail:</span> <asp:TextBox id="tEmail" runat="server" CssClass="pos110" Text="alpha@ms.com" /> <asp:RequiredFieldValidator id="emailReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ControlToValidate="tEmail" ErrorMessage="This field is reqiured"/> <asp:RegularExpressionValidator id="emailRegExpValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ControlToValidate="tEmail" ErrorMessage="E-mail syntax is incorrect" ValidationExpression=".+@.+\..+"/> <br /><br /> </div> </form></body></html> Анализируя код, обратите внимание на то, что использованы различные типы валидаторов, и некоторым элементам приписано по два валидатора. Настройте RangeValidator в обрабочике события Load так , как показано ниже. ageRangevalidator.MinimumValue = DateTime.Now.AddYears(-55).ToShortDateString(); ageRangevalidator.MaximumValue = DateTime.Now.AddYears(-23).ToShortDateString(); Запустите приложение и проверьте его функционирование. Обратите внимание на то, что валидаторы начинают оперативно работать лишь после начала ввода и перевода фокуса из одного поля в другое. Анализ скрипта, генерируемого валидаторами Попытаемся разобраться, где прячется клиентский скрипт и как он выглядит. Для того, чтобы увидеть код скрипта, который был сгенерирован ядром ASP.NET при реализации валидаторов, в брузере дайте команду View→Source. В окно Notepad будет выведен HTML-код страницы, в котором вы обнаружите множество скриптовых вставок. Начальной зацепкой в разгадывании секретов внедренного скрипта является глобальная переменная Page_Validators, объявление которой вы найдете в одном из блоков скрипта, работающего на стороне клиента. var Page_Validators = new Array( document.getElementById("nameReqValidator"), document.getElementById("ageReqValidator"), document.getElementById("ageRangevalidator"), document.getElementById("artReqValidator"), document.getElementById("articlesCmpValidator"), document.getElementById("emailReqValidator"), document.getElementById("emailRegExpValidator")); Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Как видите, Page_Validators — это массив ссылок на объекты, которые являются отображениями валидаторов в клиентском коде. Если мы поищем эти объекты в результирующем документе, то убедимся, что ASP.NET runtime заменила все валидаторы простыми контейнерами типа span. Например: <span id="nameReqValidator" class="pos250" style="color:Chartreuse;visibility:hidden;">You must enter your name</span> Она же сгенерировала скрипт, поддерживающий динамику работы этих элементов. Например, мы обнаруживаем блок скрипта, где объявлены переменные, ссылающиеся на указанные объекты типа span. Рассмотрим одну из них: var nameReqValidator = document.all ? document.all["nameReqValidator"] : document.getElementById("nameReqValidator"); nameReqValidator.controltovalidate = "tName"; nameReqValidator.errormessage = "You must enter your name"; nameReqValidator.evaluationfunction = "RequiredFieldValidatorEvaluateIsValid"; nameReqValidator.initialvalue = ""; Так как коллекция всех объектов документа—document.all не является стандартом JScript (она является MS расширением языка JScript), то ее поддерживают не все браузеры. Здесь вы видите, как с помощью тернарной операции (? :) обыгрывается эта ситуация. Заметим, что метод getElementById поддерживают все современные браузеры. Оператор вида: nameReqValidator.controltovalidate = "tName"; определяет свойство объекта языка JScript, обращение к которому допускает следующий синтаксис: var n = nameReqValidator["controltovalidate"]; Второй зацепкой в разгадывании секретов сгенерированного скрипта является значение атрибута onsubmit тега <form>. onsubmit="javascript:return WebForm_OnSubmit();" Этот атрибут определяет функцию, которая будет вызвана при попытке завершить работу с формой. Функцию WebForm_OnSubmit вы обнаружите в том же тексте результирующего HTML-документа. function WebForm_OnSubmit() { if (typeof(ValidatorOnSubmit) == "function" && ValidatorOnSubmit() == false) return false; return true; } Мне очень хочется упростить ее до: if (typeof(ValidatorOnSubmit) == "function") return ValidatorOnSubmit();. Раскручивая цепь дальше, поищем функцию ValidatorOnSubmit. Ее алгоритм таков. function ValidatorOnSubmit() { if (Page_ValidationActive) return ValidatorCommonOnSubmit(); else return true; } На этом цепь расследования обрывается, так как функция ValidatorCommonOnSubmit в явном виде отсутствует в клиентском коде разметки. Но чудес не бывает и она безусловно присутствует в неявном виде. Этот вывод можно сделать на основании того факта, что клиентский код содержит такой скриптовый блок. <script src="/MySite/WebResource.axd? d=Stf6-MaxK5hYAxKoDrNuMYwIZZk9A7D6kiNGDitVzZU1&amp;t=633464836663437500" type="text/javascript"></script> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Этот код описывает ссылку на обработчик Web-ресурсов (httpHandlers). Атрибут src элемента <script> описывает ссылку на обработчик WebResource.axd. Вы можете убедиться в этом, рассмотрев секцию <httpHandlers> файла настроек machine.config. <add verb="GET" path="WebResource.axd" type="System.Web.Handlers.AssemblyResourceLoader" /> Этот XML-элемент описывает новый тип обработчика (handler), AssemblyResourceLoader, который обслуживает клиентские файлы. Он использует при этом параметры строки запроса (querystring) для идентификации ресурсов и сборок, в которых они содержатся. Вы видите эти параметры в строке, присвоенной атрибуту src элемента <script>. Параметр t, например, означает timestamp (временную метку), а параметр d — хеш-код внедряемого ресурса. Сам ресурс найти не так просто, но можно быть уверенным, что он то и содержит функции: ValidatorCommonOnSubmit и ValidatorOnLoad. Отметим, что предыдущая версия .NET Framework в меньшей степени скрывала внедряемый скрипт и вы могли найти код всех функций в файле WebUIValidation.js, который был в папке С:\Inetpub\wwwroot\aspnet_client\system_web\1_1_4322. Теперь скрипт прячется в файлах ресурсов, которые обнаружить не просто. Custom Validator Элемент управления типа CustomValidator позволяет применить специальную процедуру проверки данных, вводимых пользователем. С помощью элемента CustomValidator мы можем осуществить оперативную проверку на стороне клиента, а затем добавить еще одну — на стороне сервера. Если вы (двойным щелчком в режиме дизайна) добавите к элементу обработчик события ServerValidate, то в нем сможете реализовать произвольный алгоритм проверки данных, например, обратиться к базе данных и сверить данные, введенные пользователем, с какими-то полями этой базы. Рассмотрим, как добавить CustomValidator в уже существующую страницу Validators.aspx и настроить его параметры. Реализуем следующий сценарий ввода и проверки данных. С помощью обычных меток выведем тестовый вопрос, добавим элемент типа asp:DropDownList, в котором будут варианты ответов, и привяжем к этому списку элемент типа CustomValidator, который будет реагировать на выбор пользователя с помощью обработчика события ServerValidate на стороне сервера, затем добавим проверку и на стороне клиента. Откройте Validators.aspx в режиме дизайна. Перейдите на вкладку Standard панели инструментов Toolbox, возьмите на ней элемент типа Label и положите его на форму над кнопкой Submit. В окне свойств задайте текст метки (подсказка пользователю): "Answer the following question, please:", измените шрифт и цвет текста. Добавьте еще один элемент типа Label (в нем будет сам тестовый вопрос). Задайте текст метки: "John's family has 6 daughters. Each daughter has 2 brothers. How many children are there in John's family?", измените шрифт и цвет текста. Добавьте на форму элемент типа DropDownList и введите в его коллекцию ListItemsCollection (см. окно свойств, строка Items) такие вариаты ответов: 18, 12, 6, 8 (это объекты типа ListItem). Свойство ID списка задайте равным ddlTest. Справа от списка положите элемент типа CustomValidator и задайте ему такие свойства: ID = "custValid", ErrorMessage = "Think It Over", ControlToValidate = "ddlTest". Совершите двойной щелчок над CustomValidator, который генерирует реакцию на событие ServerValidate, расположенную в файле с кодом поддержки, и отредактируйте код функции обработки, как показано ниже. protected void custValid_ServerValidate(object source, ServerValidateEventArgs e) { custValid.Text = "Sorry, this is wrong"; e.IsValid = ddlTest.SelectedIndex == 3; } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Вновь откройте Validators.aspx в режиме дизайна. Выделите валидатор и установите его свойство ClientValidationFunction в значение Test. Перейдите в режим редактирования кода. Добавьте JScript-функцию с именем Test, которая будет выполняться на стороне клиента. <script type="text/jscript"> function Test (source, arg) { arg.IsValid = arg.Value == 8; } </script> Добавьте две серверные кнопки типа asp:Button (ID="bCheck", Text="Check") и (ID="bSubmit", Text="Submit"). Запустите приложение, выберите неправильный ответ. Страница примет вид, который показан на рис. 2.4. Выбирая различные строки в выпадающем списке, обратите внимание на работу клиентского скрипта. Он работает всегда. Серверная реакция на событие ServerValidate откладывается до того момента, пока документ не отправится на сервер. Это происходит в момент нажатия кнопки Submit. Чтобы убедиться в срабатывании серверного скрипта, мы намеренно изменяем текст сообщения валидатора. Старый текст: ErrorMessage="Think It Over" заменяется новым, который указан в функции обработки события ServerValidate. Рис. 2.4. Страница с валидатором типа CustomValidator Здесь есть тонкость, заключающаяся в том, что серверный код имеет шанс сработать только если клиентская проверка будет успешна и пропустит данные на сервер. Поэтому сначала выберите правильный ответ, нажмите кнопку Submit, затем выберите неправильный ответ и убедитесь, что текст ErrorMessage изменился и стал равным: "Sorry, this is wrong". Итак, серверный скрипт (при наличии клиентского) работает не всегда (только в отсутствии ошибок) и поэтому почти теряет свой смысл. Правда, можно придумать более изощренный сценарий, когда он всетаки будет иметь смысл. Если клиент отключил скрипт, то серверный скрипт будет работать в любом случае. Вы знаете, что браузер позволяет рарешить HTML-страницам выполнять JScript или запретить. Проверьте настройки вашего браузера и временно запретите скрипт. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Сервис→Свойства обозревателя→Безопасность→Местная интрасеть→Параметры безопасности→Сценарии →Активные сценарии→Отключить Проверьте работу валидаторов и не забудьте вновь разрешить скрипт (. . . →Разрешить), иначе мы не сможем исследовать его влияние на работу страниц ASPX. Суммарная проверка Каждый валидатор умеет генерировать сообщение об ошибке, но он не прерывает ход обработки данных страницы. Перед тем, как начать обработку данных страницы надо убедиться, что все они соответствуют ограничениям, наложенным валидаторами. ASP.NET предоставляет такую возможность с помощью элемента управления ValidationSummary, или путем опроса свойства IsValid, которым обладает каждый объект класса Page. Валидатор типа ValidationSummary ничего не проверяет, но он составляет и выводит отчет о результатах проверок данных всеми другими валидаторами, имеющимися на странице. Он работает на стороне сервера, а проверка с помощью свойства IsValid — либо на стороне клиента, либо на стороне сервера. Самым простым способом резюмировать ошибки ввода данных является вставка валидатора ValidationSummary. Добавьте в конец формы этот элемент и установите его свойства, как показано ниже. <asp:ValidationSummary id="valSum" runat="server" HeaderText="Please correct these errors:"/> Запустите приложение и проанализируйте реакцию элемента ValidationSummary, стирая поля ввода и нажимая кнопку Check. Обратите внимание на то, что событие нажатия кнопки нами не обрабатывается, мы не связывали с ним никакой функции. Того факта, что кнопка является серверным элементом, достаточно для посылки уведомлений об изменениях. Элемент ValidationSummary автоматически срабатывает каждый раз, когда изменяется свойство Page.IsValid, а это происходит при нажатии любой серверной кнопки и текст сообщения элемента ValidationSummary появляется на форме в случае наличия ошибок в данных формы. Для проверки этого факта временно вставьте обычную кнопку <input type="button"> (возьмите ее на вкладке HTML панели Toolbox), повторите опыт с этой кнопкой и убедитесь, что ValidationSummary молчит. Полный код страницы приведем ниже, а сейчас рассмотрим свойство страницы IsValid. Свойство страницы IsValid Это свойство резюмирует результаты проверок всех валидаторов, сворачивая их с помощью логической операции AND. Полученный таким образом результат, можно как-то отобразить на странице. Обычно к свойству IsValid обращаются при обработке нажатия серверной кнопки, но возможны и другие варианты. Здесь имеется довольно тонкий момент, который не освещен в документации. По поводу документации можно сказать, что она довольно часто отстает от нововведений Microsoft. Информация в книгах отстает от реальности еще больше. Поэтому, чтобы ответить на вопрос: "Как это работает?", необходимо анализировать код. Это требует времени и терпения. Добавьте метку типа <asp:Label> (ID="sValid"), в которой будет отображено свойство IsValid. Создайте обработчик нажатия кнопки bCheck, совершив над ней двойной щелчок в режиме дизайна. В тело обработчика вставьте вызов CheckValid();. С помощью IntelliSense cоздайте этот метод и вставьте в него код, как показано ниже. protected void bCheck_Click(object sender, EventArgs e) { CheckValid(); } void CheckValid() Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { if (Page.IsValid) { sValid.ForeColor = Color.DarkGreen; sValid.Text = "Page is valid!"; } else { sValid.ForeColor = Color.Red; sValid.Text = "There are errors. . ."; } } Установите точку останова на единственной строке кода метода bCheck_Click, запустите приложение в режиме отладки и, ничего не вводя, нажмите кнопку Check. Честно созданный обработчик события Click не работает. Такие сюрпризы могут надолго выбить из колеи, если забыть про то, что валидаторы автоматически работают на стороне клиента. Логика их работы такова, что пока страница содержит ошибки все серверные кнопки не работают. Поэтому попасть внутрь функции CheckValid нет никаких шансов. По этой же причине невозможно попасть в ветвь else условного оператора if (Page.IsValid). В то же время в ветвь if управление передается. Это происходит, когда данные введены без ошибок (не нарушив требований ни одного валидатора), или когда на странице отсутсвуют валидаторы. Потусторонними силами, вмешивающимися в процесс обработки события Click, является код, генерируемый ASP.NET engine. Напомню, что вы всегда можете его увидеть, дав (в браузере) команду View→Source. В этом коде обе серверные кнопки типа asp:Button преобразуются в HTML-элементы input (типа submit) с довольно хитрой обработкой события onclick. При наличии ошибок в данных отсыл данных на сервер не происходит, что экономит время (и деньги). В таком режиме ветвь else обработчика будет бездействовать всегда, поэтому она не имеет смысла и ее следует убрать. Без ответа остался такой вопрос. Когда следует производить анализ Page.IsValid? Если на странице присутствуют валидаторы, то они запрещают обращаться к серверу, пока данные страницы содержат ошибки. Если же ошибок нет, то анализ if (Page.IsValid) на стороне сервера не имеет смысла. Ответ на этот вопрос становится очевидным, если знать, что иногда проверка на стороне клиента не производится. Во-первых, серверный код может запретить проверку путем установки свойства EnableClientScript для одного валидатора или всех валидаторов в значение false. Во-вторых, можно полностью отключать валидаторы программным способом (временной установкой их свойства Enabled в значение false). В третьих, проверка на стороне клиента не может быть произведена, если пользователь запретил браузеру выполнять JScript. Во всех этих случаях анализ свойства Page.IsValid на стороне сервера имеет смысл. Управление валидаторами Существует возможность выключить (или включить) способность производить клиентские проверки для всех валидаторов конкретной страницы. Для этого можно, например, вставить внутрь метода Page_Load такой код. foreach (BaseValidator bv in Page.Validators) bv.EnableClientScript = false; Класс BaseValidator является родителем всех классов, управляющих валидаторами. Он определен следующим образом. public abstract class BaseValidator : Label, IValidator Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Вы можете программным способом (на стороне сервера) включить или выключить любой из валидаторов. Например: custValid.EnableClientScript = bMyCheck.Checked; Здесь подразумевается, что bMyCheck является элементом типа <asp:CheckBox>, который введен в страницу для управления валидатором типа <asp:CustomValidator>. Атрибут ErrorMessage для любого из валидаторов можно установить таким образом. ErrorMessage="<img src='../Images/none.ico' width='18px' height='18px' />" В этом случае клиент увидит не текст, а изображение. Причем ValidationSummary тоже отобразит картинку. В одной из книг [1] приводится такой способ реакции на ошибку. ErrorMessage="<bgsound src='C:\\WINDOWS\\Media\\tada.wav' />" Звук tada.wav воспроизводится при загрузке страницы, так как начальная установка содержит ошибку. Смотрите справку по HTML-элементу bgsound. Этот элемент имеет атрибут для управления количеством повторений звукового файла. Испытайте, например, значение: ErrorMessage="<bgsound src='С:\WINDOWS\Media\tada.wav' loop='-1' />". При отправке данных страницы на сервер все валидаторы срабатывают автоматически. Если на странице существует множество полей ввода и связанных с ними валидаторов, а также несколько серверных кнопок, провоцирующих отсыл данных на сервер, то иногда целесообразно проверять не все поля данных сразу, а только часть из них. Например, при нажатии на кнопку A надо проверить данные каких-то трех полей ввода, а при нажатии на кнопку B — двух других полей. В .NET Framework 2.0 появилась такая возможность. Она реализована с помощью атрибутов ValidationGroup. Вставьте атрибут ValidationGroup="gA" в валидаторы первых трех кнопок, а также в кнопку A, Вставьте атрибут ValidationGroup="gB" в оставшиеся валидаторы, а также в кнопку B. Теперь каждая кнопка включает валидаторы только своей группы. Значения атрибутов групп ("gA" и "gB") произвольны, но должны совпадать внутри группы. Имеется еще одна возможность управления валидаторами. Если для валидатора установить атрибут SetFocusOnError="true", то (после проверки на сервере) фокус ввода переходит в то поле, которое ассоциировано с данным валидатором, и пользователь может вводить исправление. Если несколько валидаторов обнаружили ошибки, то фокус переходит в поле ввода, объявленное раньше других. 2.5. Видоизмененный элемент управления (Custom Server Control) В данный момент очевидно нарушение логики поведения страницы. Оно состоит в том, что текст "Page is valid!" появляется в момент нажатия кнопки Check (если позволяют валидаторы) и остается видимым до тех, пока не будет закрыта страница. Даже если вы испортите корректные данные и вновь нажмете кнопку Check, сообщение "Page is valid!" будет отображено. Стандарт интерфейса пользователя (поведения страницы при вводе данных) требует убирать это сообщение в момент, когда изменяются какие-либо входные данные, то есть, когда какое-либо из полей ввода возбуждает событие TextChanged (в JScript ему соответствует событие onchange). В связи с этим возникает желание управлять текстом метки sValid с помощью скрипта на стороне клиента. К сожалению, это не так просто сделать. Если мы попытаемся вставить атрибут onchange в любой из элементов типа asp:TextBox, то увидим, что он недопустим, несмотря на то, что этот атрибут великолепно работает в HTML-аналоге — элементе <input type="text">. Серверные компоненты, которые являются основным козырем ASP.NET, не приспособлены для обработки событий на стороне клиента (разработчики, видимо, и не ставили такой цели), но они добавили в Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 серверные элементы возможность генерации и регистрации (в Microsoft очень любят эту процедуру) скрипта на стороне клиента. Один из способов регистрации скрипта (метод RegisterClientScriptBlock) мы уже рассматривали, это. Теперь рассмотрим способ регистрации нового типа элемента, который наследует все функции asp:TextBox, но имеет дополнительный атрибут onchange. Так как нам придется довольно сильно изменить код страницы, то создайте новую форму ValidatorsScript.aspx, Cкопируйте в нее существующий код страницы Validators.aspx. Не забудьте про код поддержки, в котором присутствуют обработчики событий. Разработка Custom Server Control начинается с создания нового класса, который наследует функциональность одного из существующих классов (потомков класса System.Web.UI.Control). Мы создадим класс MyTextBox, производный от TextBox. Цель: добавить к унаследованной функциональности возможность реагировать на событие onchange (изменение текста и перевод фокуса). В новом классе следует переопределить виртуальную функцию AddAttributesToRender. Она вызывается каркасом ASP.NET перед тем, как серверный элемент TextBox будет преобразован в HTML-аналог <input>. В переопределенной (override) версии метода AddAttributesToRender надо добавить атрибут onchange. Обработчиком этого события выступит функция, которую мы назовем ClearValid и добавим в скрипт, работающий на стороне клиента. Папка App_Code Внутри папки App_Code должны располагаться вспомогательные файлы, не являющиеся страницами сайта и реализующие вашу бизнес-логику (мне не нравится этот термин, но, к сожалению, он стал стандартом. Непонятно, при чем здесь business). В папке App_Code вы можете создать свою собственную, разветвленную файловую структуру. Все файлы, расположенные в ней, будут откомпилированы и результат попадет в dll Web-приложения, которое генерирует выходные HTML-документы всех его страниц. Если вы хотите иметь исходные файлы с кодом на разных языках программирования, то их следует размещать особым образом. Например, код на C# вы помещаете в папку ../App_Code/One, а код на MС++ в папку ../App_Code/Two. ASP.NET выбирает нужный компилятор автоматически, но нельзя смешивать языки программирования в одной и той же папке. Кроме того, в файле конфигурации (Web.config—для приложения или machine.config—для всех Web-приложений сервера) следует указать факт наличия папок с файлами на разных языках (каких—разберется сама ASP.NET). Сейчас всего этого делать не надо, но в дальнейшем примите к сведению. <compilation debug="false"> <codeSubDirectories> <add directoryName="One" /> <add directoryName="Two" /> </codeSubDirectories> </compilation> Теперь опишем то, что надо сделать в данный момент. Создайте App_Code (команда Add ASP.NET Folder→App_Code контекстного меню проекта). В эту папку добавьте новый файл (команда Add New Item→Class). Задайте имя файла (и класса) MyTextBox. Вы увидите заготовку нового класса, файл которого помещен в папку App_Code. Вы видите, что класс MyTextBox не вложен в какое-либо пространство имен, а все вновь создаваемые элементы управления типа Custom Controls должны жить в уникально идентифицированных и зарегистрированных пространствах имен. Так как мы собираемся создать новый элемент управления с нестандартным обликом и поведением, то нам надо вложить код класса MyTextBox внутрь пространства имен. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Это требование объясняет наличие префикса asp: у всех элементов типа Web Server Controls. Префикс задает (определяет) уникальное пространство имен, где определены серверные элементы ASP.NET. Замените текст заготовки класса MyTextBox на тот, что приведен ниже. using using using using using using System; System.Web; System.Web.UI; System.Web.UI.WebControls; System.Web.UI.HtmlControls; System.Security.Permissions; namespace MySite { [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), ToolboxData("<{0}:MyTextBox runat=\"server\"> </{0}:MyTextBox>")] public class MyTextBox : TextBox { protected override void AddAttributesToRender(HtmlTextWriter w) { base.AddAttributesToRender(w); w.AddStyleAttribute(HtmlTextWriterStyle.Color, "#aa0000"); w.AddStyleAttribute(HtmlTextWriterStyle.Padding, "1px"); w.AddStyleAttribute(HtmlTextWriterStyle.FontWeight, "bold"); w.AddStyleAttribute(HtmlTextWriterStyle.FontFamily, "Arial"); w.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "solid"); w.AddStyleAttribute(HtmlTextWriterStyle.BorderWidth, "1px"); w.AddStyleAttribute(HtmlTextWriterStyle.BackgroundColor, "#ffffdd"); w.AddAttribute("onchange", "ClearValid()"); } } } Атрибуты в квадратных скобках, которые вы видите перед объявлением класса, задают установки, необходимые системе безопасности. Концепция Windows Security весьма запутана, поэтому не буду приводить никаких объяснений, при желании вы найдете их в MSDN. Здесь мы определяем новый класс, производный от существующего TextBox. Цель: изменить наследственность обычного поля (окна) редактирования текста, управляемого классом TextBox, для того, чтобы отображать текст красным цветом, более плотным шрифтом и т. д. Кроме того, мы хотим добавить в элемент новое поведение, которое проявится на стороне клиента. Оно состоит в том, что при изменении текста в окне MyTextBox будет стираться текст в каком-то другом окне. Например, мы будем гасить сообщение: "Page is Valid", которое утверждает, что все данные, введенные в поля редактирования типа MyTextBox, проверены, то есть, удовлетворяют каким-то заданным условиям. Виртуальный метод AddAttributesToRender, унаследованный от классов WebControl→TextBox, позволяет корректировать HTML-атрибуты и компоненты стиля воспроизводимого браузером элемента управления (в нашем случае родительским элементом является asp:TextBox). Переопределяя этот метод в классе MyTextBox, мы вмешиваемся в процесс отображения стандартного элемента и корректируем его, но мы обязаны вызвать родительскую версию виртуального метода AddAttributesToRender. base.AddAttributesToRender(w); // Стандартный способ отображения Добавляя атрибуты стиля и реакции на событие на onchange, мы вносим специфические особенности, определяющие облик и поведение всех элементов типа MyTextBox. Вы, возможно, помните, что onchange — это событие, возникающее после внесения изменений в поле, обслуживаемое классом MyTextBox. Оно возбуждается в момент выхода фокуса из редактируемого элемента (если содержимое было изменено). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 2.5. Замена элементов TextBox на элементы типа CustomControls Важен факт, что событие onchange будет обработано на стороне клиента. Обработчиком события onchange выступит функция ClearValid, которую мы добавим в скрипт на стороне клиента. Текст скрипта (либо вручную, либо программно) надо добавить в текст страницы, где будут использованы элементы типа MyTextBox. Приведем список изменений, которые вы должны ввести в текст страницы ValidatorsScript.aspx. После директивы <%@ Page вставьте директиву регистрации префикса mySoft. <%@ Register TagPrefix="mySoft" Namespace="MySite" %> Префикс mySoft придуман на ходу, но после регистрации с помощью директивы <%@ Register %> он определяет новое пространство имен mySoft. Иногда говорят, что префикс ассоциирован с пространством имен. Замените все вхождения asp:TextBox на mySoft:MyTextBox. Это можно сделать, отдав команду Ctrl+H (Find and Replace). Теперь все обычные окна редактирования (Web Server Controls) превратились в необычные (Custom Server Controls). В блок <script> добавьте простую функцию, которая будет вызываться каждый раз, когда пользователь изменит содержимое поля ввода, управляемого классом MyTextBox. function ClearValid() { var o = document.getElementById("sValid"); if (o != null) o.innerText = ""; } Новый элемент MyTextBox можно поместить на ToolBox для того, чтобы можно было работать с ними в режиме дизайна (см. MSDN, Walkthrough: Developing and Using a Custom Server Control). Но я привык не доверять дизайнеру, особенно в рамках сети, где действуют различные права и ограничения. В этих условиях редактор часто подводит. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Нам важен факт, что все четыре поля ввода (объекты класса TextBox) замещены объектами класса MyTextBox. Префикс mySoft выполняет роль пространства имен и присутствует при всех элементах нового пользовательского класса MyTextBox. Он позволяет отличить MyTextBox от элемента с таким же именем, но разработанного другим программистом. Запустите страницу ValidatorsScript.aspx и убедитесь, что цель достигнута. При каждом изменении текста в полях ввода (и переводе фокуса в другое поле) сообщение "Page is valid!" исчезает и это происходит в клиентском браузере, а не на сервере. 2.6. Пользовательские элементы управления Наряду с серверными элементами: HTML Server Controls, Web Server Controls и Custom Server Controls, ASP.NET позволяет разрабатывать пользовательские элементы управления (User Server Conrols), которые в дальнейшем можно использовать в качестве готовых строительных блоков, ускоряющих процесс разработки Web-приложений. Они пришли на смену include-файлам, использующимся в технологии ASP. Элементы типа User Server Conrols не следует путать с элементами типа Custom Server Controls, технологию создания и использования которых мы затронули в предыдущем разделе. Главным отличием User Server Conrols является то, что их код генерируется динамически (во время выполнения кода страницы) и поэтому они должны целиком импортироваться в использующее их Web-приложение, в то время как Custom Server Controls — это уже скомпилированный код, который может существовать в единственном экземпляре (если он инсталлирован в глобальном кэше сборок — global assembly cache). User Server Conrol — это частный случай страницы ASP.NET, которая легко импортируется в другую Webформу одного и того же приложения. User Conrol также содержит элементы интерфейса, скрипт, реагирующий на события, и код поддержки (code-behind), компилируемый ядром ASP.NET и сплавляемый вместе с кодом разметки страницы, в которую внедрен данный элемент. Пользовательские элементы управления отличаются от обычных страниц тем, что они не могут содержать блоки <head>, <body> и <form>, имеют имеют расширение ASCX (Active Server Control) и не могут работать отдельно от так называемой host page — страницы, которая импортирует код элемента. Обычные страницы ASPX имеют директиву <@ Page, а элементы ASCX — директиву <@ Control, с помощью которой они свзываются с кодом поддержки и host page. Один и тот же элемент можно многократно использовать даже в рамках одной страницы. Переменные, управляющие поведением элементов, входящих в компонент ASCX, живут в своем собственном пространстве имен, что исключает коллизии имен и снижает уровень так называемого засорения имен (name pollution). Ниже мы собираемся создать три пользовательских элемента управления, два из которых можно использовать как составные части других Web-страниц. Первым User Conrol будет блок кода (MyLogin.ascx и MyLogin.ascx.cs), предоставляющий интерфейс для ввода данных о пользователе. Он, кроме полей ввода, использует валидаторы и можно считать, что большая часть его кода уже разработана в предыдущем разделе. Вторым элементом будет панель управления MyToolbar.ascx. Элементы подобного типа обычно вставляют во все страницы какого-либо сайта. Они упрощают навигацию по основным разделам сайта. Третьим элементом будет управляемая панель с комбинацией таблиц, функционирующих наподобие дерева. Такая панель располагается в левой части окна браузера и служит для навигации по множеству документов, например, главам книги или разделам курса лекций. Компонент регистрации На рис. 2.6 показана страница, в которую вложен пользовательский элемент (User Conrol, или блок кода MyLogin.ascx). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 2.6. Элемент типа UserControl, внедренный в страницу регистрации Элемент собирает сведения, необходимые для регистрации пользователя. Текст вверху окна браузера принадлежит странице, которая приютила User Conrol, сам же элемент расположен внутри темной панели (элементе <div>). Приведем последовательность действий, необходимых для создания элемента MyLogin. В окне Solution Explorer выделите папку Misc и дайте команду Add New Item→Web User Control. Задайте имя — MyLogin.ascx. Положите на форму элемент типа div (он находится на вкладке HTML панели Tollbox) и задайте стиль, который существует в таблице стилей, например: <div class="darkShadow">. Выделите и скопируйте в буфер обмена элементы управления, которые составляют суть страницы ValidatorsScript.aspx, поля ввода: tName, tBirth, tEmail, соответствующие им метки, валидаторы, метку sValid и две кнопки Check и Submit. Восстановите из буфера обмена все эти элементы управления, разместив их на панели div. Работая таким образом, начинаешь ценить труд дизайнеров. Довольно сложно выполнить хотя бы некоторые требования дизайна, так как далеко не все компоненты стиля предсказуемы. В рамках User Conrol они почему-то ведут себя не так, как в рамках формы. При выборе стиля руководствуйтесь чувством меры и собственным вкусом, но вам придется идти на уступки. Так как полями ввода являются элементы типа <mySoft:MyTextBox, добавьте директиву <%@ Register TagPrefix="mySoft" Namespace="MySite" %> Внутрь тега <head> добавьте ссылку на таблицу стилей. <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> Здесь надо отметить, что в студии 2005 подобный прием не проходит. Каскадные стили из таблицы неправильно работают в рамках User Control. В студии 2008 они работают, но неточно. Облик элемента <mySoft:MyTextBox>, лежащего на форме, отличается от облика того же элемента, расположенного в User Control. В таких случаях иногда помогает следующий прием: принесите копии нужных стилей в файл ascx и поместите их в блок <style>. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Ниже приведен код разметки компонента MyLogin, который получился в результате выполнения описанных операций. Обратите внимание на директиву <@ Control, а также на то, что код не имеет элементов <head>, <body> и <form>. <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyLogin.ascx.cs" Inherits="Misc_MyLogin" %> <%@ Register TagPrefix="mySoft" Namespace="MySite" %> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> <div class="darkShadow"> <script type="text/jscript"> function ClearValid() { var o = document.getElementsByTagName ("span"); for (var i=0; i<o.length; i++) { if (o[i].id.indexOf("sValid") != -1) { o[i].innerText = ""; break; } } } </script> <h3>Fill in this form, please</h3> <span>Your Name:</span> <mySoft:MyTextBox id="tName" runat="server" CssClass="pos110" /> <asp:RequiredFieldValidator id="nameReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ErrorMessage="You must enter your name" ControlToValidate="tName"/><br /><br /> <span>Birth Date:</span> <mySoft:MyTextBox id="tBirth" runat="server" CssClass="pos110" /> <asp:RequiredFieldValidator id="ageReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ErrorMessage="This field is reqiured" ControlToValidate="tBirth"/> <asp:RangeValidator id="ageRangevalidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ErrorMessage="Applicants must be between 23 and 55" ControlToValidate="tBirth" Type="Date"/><br /><br /> <span>E-mail:</span> <mySoft:MyTextBox id="tEmail" runat="server" CssClass="pos110" /> <asp:RequiredFieldValidator id="emailReqValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ErrorMessage="This field is reqiured" ControlToValidate="tEmail"/> <asp:RegularExpressionValidator id="emailRegExpValidator" runat="server" CssClass="pos250" ForeColor="Chartreuse" ErrorMessage="E-mail syntax is incorrect" ControlToValidate="tEmail" ValidationExpression=".+@.+\..+"/><br /><br />&nbsp;&nbsp;&nbsp;&nbsp; <asp:Button id="bCheck" runat="server" BackColor="#ffffdd" Text="Check" onclick="bCheck_Click" />&nbsp;&nbsp; <asp:Button id="bSubmit" runat="server" BackColor="#ffffdd" Text="Submit" OnClick="bSubmit_Click"/>&nbsp;&nbsp; <asp:Label ID="sValid" runat="server" ForeColor="Lime" Font-Names="Comic Sans MS" /> <br /> </div> Кроме элементов, перенесенных из ValidatorsScript, здесь добавлен заголовок (элемент <h3>) и изменен цвет фона для кнопок. Функция ClearValid (обработчик события onchange всех элементов типа MyTextBox) отличается от того, что был внедрен в страницу ValidatorsScript. Он выглядит неопраданно усложненным. Но на это есть причина. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 В отличие от прерыдущего варианта использования MyTextBox, мы не можем искать элемент с индексом sValid, несмотря на то, что он существует в составе UserControl MyLogin. Дело в том, что MyLogin не работает сам по себе, он является всего лишь бесправной частью той страницы, которая воспользуется его услугами. Страница может внедрить несколько экземпляров элемента MyLogin, каждый из которых содержит элемент sValid. Кроме того, страница имеет множество своих собственных элементов. Имена и индексы этих элементов нам неизвестны, они могут совпасть с именем sValid. Поэтому ASP.NET переименовывает все элементы, входящие в состав внедренных UserControls. Перед каждым именем элемента она добавляет уникальное имя самого UserControl. Мы не можем расчитывать на то, что это правило не изменится. Поэтому, поиск нужного элемента проводится методом перебора всех панелей типа span. Мы ищем sValid в виде составной части индекса. Такое решение проходит в нашем случае, но в более сложных вариантах внедрения элементов управления надо искать другой способ решения. В код поддержки элемента (файл MyLogin.ascx.cs) внесите изменения, цель которых — повторить функциональность страницы ValidatorsScript и сэкономить усилия при вводе данных. public partial class Misc_MyLogin : UserControl { protected void Page_Load (object sender, EventArgs e) { if (!IsPostBack) { tName.Text = "Joe Doe"; tBirth.Text = DateTime.Now.AddYears(-30).ToShortDateString(); tEmail.Text = "Joe@Doe.com"; } ageRangevalidator.MinimumValue = DateTime.Now.AddYears(-55).ToShortDateString(); ageRangevalidator.MaximumValue = DateTime.Now.AddYears(-23).ToShortDateString(); } protected void bCheck_Click(object sender, EventArgs e) { if (Page.IsValid) sValid.Text = "Page is Valid"; } protected void bSubmit_Click(object sender, EventArgs e){} // Пока пусто } В данный момент наш компонент готов к тому, чтобы служить составной частью любой страницы ASP.NET, но мы покажем, как внедрять элементы такого типа чуть позже, а сейчас рассмотрим два других компонента типа UserControl. Панель управления Довольно много усилий (чтобы не сказать мучений) потребовалось для создания второго элемента управления MyToolbar.ascx. Его идея, часть кода и необходимые картинки были взяты из HTML-документа, созданного в недрах Microsoft. Попробуйте повторить действия, необходимые для создания User Control, и разработайте элемент с именем MyToolbar.ascx, показанный на рис. 2.7. Придать элементу такой облик в режиме дизайна оказалось совсем не просто. Неприятно, но факт — в разных версиях студии один и тот же код разметки отображается по-разному. Рис. 2.7. Общий вид элемента управления для навигации по страницам сайта Ниже приведен код разметки элемента, описать процесс создания которого в режиме дизайна не представляется возможным. Трудности подстерегают при попытке бесшовно объединить (на поверхности планки MyToolbar) изображения и текстовые элементы меню, которые ссылаются на страницы нашего сайта. Этот вариант получен в рамках студии 2008. Он имел несколько другой код в студии 2005. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyToolbar.ascx.cs" Inherits="Misc_MyToolbar" %> <style type="text/css"> img { border:none; } .pad {position:absolute;top:7px;} .text { font-weight:bold; font-size:10pt; font-family:Arial; color:white; text-decoration:none; vertical-align: super; padding:3%; } </style> <div style="background-image:url(../Images/BarBack.gif); background-repeat:repeatx;width:100%;left:0px;position:absolute;top:0px;height:100%;"> <img id="left" src="../Images/BarLeft.gif" alt="Left"/>&nbsp; <a href="Register.aspx" class="pad" style="left:20px;"> <img src="../Images/HomeGray.gif" alt="Home" onmousedown="src='../Images/HomeDown.gif'" onmouseover="src='../Images/HomeOver.gif'" onmouseout="src='../Images/HomeGray.gif'" /></a> <a href="mailto:Alex@avalon.ru" class="pad" style="left:45px;"> <img src="../Images/WriteGray.gif" alt="Write Us" onmousedown="src='../Images/WriteDown.gif'" onmouseover="src='../Images/WriteOver.gif'" onmouseout="src='../Images/WriteGray.gif'" /></a> <a href="Select.aspx" class="pad" style="left:70px;"> <img src="../Images/HelpGray.gif" alt="Help" onmousedown="src='../Images/HelpDown.gif'" onmouseover="src='../Images/HelpOver.gif'" onmouseout="src='../Images/HelpGray.gif'" /></a> <a href="Zoom.aspx" class="pad" style="left:120px;color:White;" onmouseover="style.color='chartreuse'" onmouseout="style.color='white'">ADO.NET Classes</a> <a href="Validators.aspx" class="pad" style="left:270px;color:White;" onmouseover="style.color='chartreuse'" onmouseout="style.color='white'">Projects</a> <a href="../HTML/First.htm" class="pad" style="left:370px;color:White;" onmouseover="style.color='chartreuse'" onmouseout="style.color='white'">Forum</a> <a href="../HTML/Details.htm" class="pad" style="left:470px;color:White;" onmouseover="style.color='chartreuse'" onmouseout="style.color='white'">About Us</a> <a href="http://www.avalon.ru"><img id="logo" src="../Images/BarMSDN.gif" alt="Training" style="position:absolute;top:-1px;right:-1px;" /></a> </div> В коде вы видите ссылки на страницы нашего сайта и других сайтов, но сами страницы, возможно, отсутствуют. Мы преследуем цель — научиться создавать и использовать компоненты ascx, а не разработать полномасштабный сайт. В коде также присутствует множество изображений (<img>) и ссылок (<a>) с изображениями. Файлы с изображениями следует расположить в отдельной папке, расположенной внутри главной папки сайта. Вспомните, что ранее мы создали папку Images. Вам надо поместить в нее нужные файлы (их можно взять из материалов курса). Теперь обсудим некоторые детали. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Иллюзия выпуклой панели создается с помощью трех изображений. Важную роль в реализации этого трюка играет повторение изображения BarBack.gif вдоль горизонтальной оси (см. атрибут стиля: background-repeat:repeat-x;). Левый край выпуклой панели имитируется с помощью изображения BarLeft.gif, а правый—с помощью BarMSDN.gif. Выравнивание изображений, размещенных на панели, осуществляется с помощью каскадного стиля по имени pad. Так же поступаем с пунктами меню, которые представляют собой стилизованные гиперрсылки <a>. Процесс дизайна (борьба со швами соединений) отнимает много времени. Тонкость состоит в удалении рамок (border:none), установке подходящих компонентов стиля и управлении ими в мышиных событиях. В процессе наложения элементов обычно решающую роль играет атрибут z-index. Он определяет порядок накладываемых друг на друга элементов вдоль оси Z и, следовательно, их видимость. Нам каким-то образом удалось обойтись без явного задания атрибута z-index (видимо, благодаря порядку следования тегов). Дерево выбора Третий UserControl с именем MyTree изображает дерево документов гипотетического сайта с учебными материалами по технологии ADO.NET. На рис. 2.8. показана страница, содержащая оба компонента ASCX: MyToolbar и MyTree. Предполагается, что эти компоненты присутствуют на каждой странице сайта, кроме страницы регистрации пользователя. Код компонента заимствован из кода обычной HTML-страницы, которая сопровождает учебный курс Microsoft. Рис. 2.8. Вид страницы с двумя элементами типа UserControl Компонент MyTree.ascx значительно сложнее предыдущих двух. Он использует CSS-стили и скрипт на стороне клиента. При анализе, обратите внимание на управление динамикой отображения дерева. Узлы дерева могут раскрываться и закрываться с помощью кода JScript. Алгоритм построен на управлении Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 видимостью панелей (компонент стиля display), он использует компоненты стиля CSS, а также идентификаторы панелей div. <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyTree.ascx.cs" Inherits="Misc_MyTree" %> <style type="text/css"> img { border:none; } .arrow {border:none;} .bullet {border:none;} div.bullet {margin-left: 25pt} a {color: #0033aa} a:visited {color: #0033aa} a:active {color: #ffffff; font-weight:bold; } a:hover {color: #ffff88; font-weight:bold;} .tree {background:url(../Images/TreeBk.gif) repeat-x;width:240px; height:100%;} </style> <script type="text/jscript"> var show = true; // Флаг скрытия-открытия дерева function CloseArrows() { var divs = document.all.tags ("div"); // Коллекция всех элементов типа div //======= Скрываем все панели, кроме панели главного дерева for (var i=0; i<divs.length; i++) { if (divs[i].className == "bullet") divs[i].style.display = "none"; } // Приводим все изображения стрелочек в соответствие с состоянием закрытости дерева var imgs = document.images; for (var i=0; i<imgs.length; i++) { if (imgs[i].className == "arrow") imgs[i].src = "../Images/Arrow.gif"; } show = true; document.all.tree.style.visibility = "visible"; } function Collapsed (div) { return eval("document.all." + div + ".style.display") == "none"; } function ClickArrow (div, img) // Открытие-скрытие одного уровня иерархии { var collapsed = Collapsed (div); div = eval ("document.all." + div); img.src = collapsed ? "../Images/ArrowDownOver.gif" : "../Images/ArrowOver.gif"; div.style.display = collapsed ? "block" : "none"; } //======= Вход-выход мышиного фокуса в область раскрываемого узла дерева function OverArrow (div, img) { img.src = Collapsed(div) ? "../Images/ArrowOver.gif" : "../Images/ArrowDownOver.gif"; } function OutArrow (div, img) { img.src = Collapsed(div) ? "../Images/Arrow.gif" : "../Images/ArrowDown.gif"; } //===== Вход-выход фокуса в область ссылки на документ (нераскрываемого узла дерева) function OverLink (anchor) { Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 anchor.children[0].src = "../Images/BulletOver.gif"; } function OutLink(anchor) { anchor.children[0].src = "../Images/Bullet.gif"; } function ToggleTree() // Скрытие-открытие всего дерева { show = !show; document.getElementById("tree").style.visibility = show ? "visible" : "hidden"; // Альтернативный вариант // document.getElementById("tree").style.display = show ? "block" : "none"; } </script> <div id="openTree" style="position:absolute;top:40px;"><!-- Open Tree Button --> <img src="../Images/Open.gif" onmousedown="src='../Images/OpenDown.gif'" onclick="ToggleTree()" onmouseover="src='../Images/OpenOver.gif'" onmouseout="src='../Images/Open.gif'" alt="Open the Table of Contents" /> </div> <!-- Tree Panel --> <div class="tree" id="tree" style="left:0px;position:absolute;top:32px;"> <!-- Close Tree Button --> <img src="../Images/Close.gif" style="left:10px;position:absolute;top:10px;" onmousedown="src='../Images/CloseDown.gif'" onclick="ToggleTree()" onmouseover="src='../Images/CloseOver.gif'" onmouseout="src='../Images/Close.gif'" alt="Close the Table of Contents" /><br/><br/>&nbsp;&nbsp; <a onmouseover="OverLink(this)" onmouseout="OutLink(this)" href="Header.aspx"> <img src="../Images/Bullet.gif" alt="" /> Programming with ADO.NET</a><br/>&nbsp;&nbsp; <!-- Top layer tree node. It has nested nodes --> <a onclick="ClickArrow('genNodes', children[0])" onmouseover="OverArrow('genNodes', children[0])" onmouseout="OutArrow ('genNodes', children[0])"> <img class="arrow" src="../Images/ArrowDown.gif" alt="" />General</a><br /> <!-- Nested nodes panel --> <div class="bullet" id="genNodes"> <a onmouseover="OverLink(this)" onmouseout="OutLink(this)" href="../HTML/QueryString.htm" > <img src="../Images/Bullet.gif" alt="" />PowerPoint Presentations</a><br/> <a onmouseover="OverLink(this)" onmouseout="OutLink(this)" href="../HTML/Details.htm"> <img src="../Images/Bullet.gif" alt="" />Setup Guides</a><br/> <a onmouseover="OverLink(this)" onmouseout="OutLink(this)" href="../HTML/Dynamic.htm"> <img src="../Images/Bullet.gif" alt="" />Software Requirements</a><br/> <a onmouseover="OverLink(this)" onmouseout="OutLink(this)" href="../HTML/SplitQuery.htm" > <img src="../Images/Bullet.gif" alt="" />Student Course Files</a> </div>&nbsp;&nbsp; <!-- Top layer tree node. It has nested nodes --> <a onmouseover="OverArrow('cntNodes',children[0])" onclick="ClickArrow('cntNodes', children[0])" onmouseout="OutArrow ('cntNodes', children[0])"> <img class="arrow" src="../Images/ArrowDown.gif" alt="" />Table of Contents</a> <!-- Nested nodes panel --> <div class="bullet" id="cntNodes"> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <a onmouseover="OverLink(this)" <img src="../Images/Bullet.gif" <a onmouseover="OverLink(this)" <img src="../Images/Bullet.gif" <a onmouseover="OverLink(this)" <img src="../Images/Bullet.gif" <a onmouseover="OverLink(this)" <img src="../Images/Bullet.gif" </div> </div> onmouseout="OutLink(this)" href="../HTML/First.htm"> alt="" />Distributed applications </a><br/> onmouseout="OutLink(this)" href="Zoom.aspx"> alt="" />Data Sources</a><br/> onmouseout="OutLink(this)" href="ZoomServer.aspx"> alt="" />Building DataSets</a><br/> onmouseout="OutLink(this)" href="Default.aspx"> alt="" />Reading and Writing XML</a><br/> Использование коллекции document.all делает код непереносимым в некоторые браузеры, так как они поразному реализуют концепции DOM. Если вам захочется подправить код, то замените оператор document.all.tags ("div"); на document.getElementsByTagName ("div");. Отметим детали. В функции CloseArrows используется класс CSS с именем bullet. Это имя позволяет найти и схлопнуть (collapse), то есть сделать невидимыми, все панели, содержащиеся внутри узлов первого уровня. Класс с именем arrow позволяет найти и изменить изображения стрелок, помечающих узлы первого уровня. Внутри функции ClickArrow изменяется облик стрелки (узла дерева) и скрывается или раскрывается панель, содержащая узлы поддерева. Алгоритм построен на использовании идентификаторов панелей <div>. Общую панель с id="tree" (на ней расположено все дерево), также можно скрыть (и вновь раскрыть) с помощью двух кнопок, точнее, двух изображений в виде кнопки (элементов типа img). В данный момент мы имеем три строительных блока, которые можно использовать при создании страниц ASPX. Вместе с файлами MyLogin.ascx, MyToolbar.ascx и MyTree.ascx мастер студии создал файлы с кодом поддержки на языке C#. По своей структуре класс Misc_MyLogin, производный от класса UserControl, напоминает класс, поддерживающий обычную страницу. В нем присутствуют обработчики событий Load и Click. Подумайте, что необходимо добавить в этот класс, для того, чтобы данные регистрации пользователя можно было использовать в рамках других страниц сайта. Так как элементы из категории UserControl работают не самостоятельно, а в рамках других страниц (host pages), то они должны давать доступ к своим данным посредством открытых свойств. Вспомогательная структура Класс Misc_MyLogin должен упростить манипуляции с данными о пользователе (name, birth, email). Для удобства передачи этих данных в другие aspx-документы целесообразно объединить их в одну структуру (назовем ее UserData). Объявление этой структуры, как и всех других вспомогательных типов, которые не являются страницами сайта, следует поместить в специальную папку с именем App_Code. Добавьте в папку App_Code новый класс (Add→Class) с именем UserData. Затем замените класс на структуру. public struct UserData { public string name, email; public DateTime birth; public UserData (string n, DateTime b, string e) { name = n; birth = b; email = e; } public override string ToString () { return name + "; Birth: " + birth + "; Email: " + email; } } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Добавьте в класс Misc_MyLogin закрытую переменную и открытое свойство типа UserData. Они нужны для управления данными, вводимыми пользователем в момент регистрации. private UserData loginData; public UserData LoginData { get { return loginData; } } Теперь покажем, как внедрить пользовательский элемент управления (компонент ascx) в Web-страницу (документ aspx). Добавьте в папку Misc новую Web-форму с именем Register.aspx и переведите ее в режим дизайна. В окне Solution Explorer выделите элемент MyLogin.ascx и с помощью операции drag-and-drop перетащите его на поверхность формы Register.aspx. В окне свойств измените идентификатор (ID) пользовательского элемента. Пусть он будет myLogin. Студия отреагирует на эти действия тем, что добавит в файл с кодом разметки (Register.aspx) директиву <@ Register>, а также код, описывающий новый User Control. Переведите страницу в режим Source. Как видите, директива: <%@ Register Src="MyLogin.ascx" TagName="MyLogin" TagPrefix="uc1" %> (с помощью префикса uc1 регистрирует пространство имен, в котором определен элемент типа MyLogin. Пространство имен задается с помощью атрибута TagPrefix, а тип — с помощью атрибута TagName. Как и ранее, переименуем (заменим все вхождения) префикса uc1 (что означает user control №1) на префикс mySoft (или любой другой, который указывает на автора или категорию элемента). Важно, чтобы значение префикса не было равно asp. <%@ Register Src="MyLogin.ascx" TagName="MyLogin" TagPrefix="mySoft" %> Замените префикс и идентификатор внедренного элемента, так, чтобы он приобрел вид: <mySoft:MyLogin id="myLogin" runat="server" /> Теперь добавим в страницу Register.aspx метку (Label) с текстом, который разъясняет цель заполнения данных формы. Для удобства пользования формой желательно добавить клиентский скрипт, который при открытии страницы в браузере задает оптимальный размер формы, переводит фокус ввода в первое поле ввода tName и выделяет содержимое этого поля. При написании кода JScript важно учесть тот факт, что внедряя User Control в состав формы, ASP.NET изменит идентификатор поля tName. К нему добавится префикс, вычисленный с учетом идентификатора внедряемого элемента (myLogin). Ниже приведен полный код страницы Register.aspx. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Register.aspx.cs" Inherits="Misc_Register" %> <%@ Register Src="MyLogin.ascx" TagName="Login" TagPrefix="mySoft" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>Register Page</title> <script type="text/jscript"> resizeTo(700,550); function Init() { var o = document.getElementById("myLogin_tName");// Префикс, добавленный ASP.NET if (o != null) { o.focus(); o.select(); } } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <body onload="Init();"> <form id="form1" runat="server" action="Header.aspx"> <div> <asp:Label ID="msg" runat="server" ForeColor="#804040" />&nbsp;<br /><br /><br /> <mySoft:Login id="myLogin" runat="server" /> </div> </form></body></html> Текст сообщения (метки msg) удобно задать в коде поддержки страницы. Такой подход позволяет изменять текст в зависимости от предыстории общения с конкретным клиентом. В данный момент наше приложение не использует механизмы памяти на стороне сервера и для него все клиенты равноправны, но по мере развития сайта эти механизмы можно включить. Рассмотрим код поддержки страницы Register. public partial class Misc_Register : Page { public UserData LoginData { get { return myLogin.LoginData; } } protected void Page_Load (object sender, EventArgs e) { msg.Text = "To view the course materials you must fill in the form.<br>" + "We guarantee confidential use of these data.<br>" + "You'll be informed of our new courses available on CD."; if (IsPostBack) // Если страница загружается повторно { Page.Validate(); // Суммируем результат проверки валидаторами if (Page.IsValid) (myLogin.FindControl("sValid") as Label).Text = "Page is valid"; } } } Ветвь условного оператора if (IsPostBack) иллюстрирует технику вычисления и вывода результата проверки данных формы всеми валидаторами на стороне сервера. Проверьте работу страницы Register.aspx с внедренным в нее элементом MyLogin.ascx. Нажмите кнопку Check и убедитесь, что результат Page.IsValid высвечивается меткой, которая спрятана внутри User Control myLogin. Удалите любой символ имени и переведите фокус в другое поле (нажмите Tab). Сообщение Page is valid должно исчезнуть. Это служит подтверждением работоспособности клиентского скрипта, который также спрятан внутри myLogin. Просмотрите HTML-код страницы и убедитесь, что в нем присутствует сгенерированный ASP.NET элемент <input> с идентификатором myLogin_tName. Если когда-либо правило идентификации полей внедряемых элементов изменится, то оператор: document.getElementById("myLogin_tName"); даст сбой. Поэтому следующая версия клиентского скрипта, видимо, будет более надежна. function Init() { var o = document.getElementsByTagName("input"); for (var i=0; i<o.length; i++) { if (o[i].id.indexOf("tName") != -1) { o[i].focus(); o[i].select(); break; } } } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 2.7. Передача данных в контекст другой страницы При нажатии на кнопку Submit компонента MyLogin как управление, так и введенные данные должны передаваться в какую-то другую страницу. В данный момент этого не происходит, так как мы не предпринимали усилий для реализации такого сценария. Другие User-компоненты (MyToolbar и MyTree) не нуждаются в механизме обмена данными, так как они предназначены для перехода к другим страницам сайта. Файлы с кодом их поддержки остаются в исходном состоянии и, видимо, имеет смысл преобразовать их в однофайловую модель (убрать файлы с кодом поддержки и скорректировать директиву <@Control>). Очевидно, что при внедрении ascx-компонента в другую страницу, реализующую другую логику поведения, кнопка Submit должна отдавать управление другому документу сайта. Поэтому в класс Misc_MyLogin придется добавить еще одну защищенную переменную и открытое свойство, с помощью которого страница (host-page), приютившая наш Control, сможет установить необходимое ей имя следующего документа (target page). Сделайте это. protected string nextPage; public string NextPage { set {nextPage = value;} } Затем добавьте код в обработчик нажатия кнопки Submit. protected void bSubmit_Click(object sender, EventArgs e) { if (Page.IsValid) { loginData = new UserData(tName.Text, Convert.ToDateTime(tBirth.Text), tEmail.Text); if (!string.IsNullOrEmpty(nextPage)) Server.Transfer(nextPage); } } В теле метода bSubmit_Click данные о пользователе собираются в одну структуру loginData. Затем управление передается другой странице (nextPage) с помощью статического метода Transfer класса HttpServerUtility. Идентификатор Server, который вы видите в коде, — это свойство класса Page, обеспечивающее доступ к объекту класса HttpServerUtility. Метод Transfer требует задать один параметр: URL той страницы, куда передается управление. Для того, чтобы host-page (Register.aspx) смогла передать данные о пользователе в контекст какой-то другой страницы, в классе Misc_Register удобно иметь открытое свойство. Оно позволит менять страницу перехода (NextPage). Мы добавили это свойство ранее, но теперь надо добавить код для установки адреса перехода к другой странице. Вставьте следущую строку внутрь метода Page_Load класса классе Misc_Register. // После вводв данных выполним переход к странице Header.aspx myLogin.NextPage = "~/Misc/Header.aspx"; В данный момент страница Header.aspx (куда мы намерены переадресовать браузер) не существует. Напомним, что переход возможен только после правильного заполнения данных о пользователе и за этим следят валидаторы. Сборка страницы из готовых элементов Покажем, как собрать страницу Header.aspx, из готовых элементов, и как передать в нее данные о пользователе, введенные в диалоге страницы Register.aspx. Вы помните, что данные хранятся в структуре UserData. Добавьте в папку Misc новую Web-форму с именем Header.aspx. Вставьте в нее два компонента: MyToolbar.ascx и MyTree.ascx. Замените префикс uc1 на mySoft, а идентификаторры компонентов упростите до: tool и tree. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 При выборе позиции компонентов на странице и сопряжения их с другими, обычными элементами, которые будут на ней, следует добавить панели <div> и позиционировать их нужным образом. Ниже приведен код разметки, полученный в результате манипуляций в режимах Design и Source. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Header.aspx.cs" Inherits="Misc_Header" %> <%@ Register Src="MyToolbar.ascx" TagName="MyToolbar" TagPrefix="mySoft" %> <%@ Register Src="MyTree.ascx" TagName="MyTree" TagPrefix="mySoft" %> <%@ Reference Page="Register.aspx" %> <!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>Header Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body style="height:100%;" onload="resizeTo(900,700);"> <form id="form1" runat="server"> <mySoft:MyToolbar ID="tool" runat="server" /> <mySoft:MyTree ID="tree" runat="server" /> <div id="header" style="position:absolute;left:300px;top:40px; height:100%;background-color:#fefeee;"><br /><br /> <asp:Label id="lblGreet" runat="server" Font-Bold="True" Font-Size="Larger" /> <br /><br /> <asp:Label id="lblMsg" runat="server" Font-Bold="True" Text=""/><br /> <img src="../Images/Splash.gif" alt="MS ADO.NET" /><br/> <span style="font-size:xx-small;"> ©2001-2002 Microsoft Corporation. All rights reserved. </span> <a href="http://www.microsoft.com/windows/ie/default.htm" target="_blank"> <img src="../Images/MSIE.gif" alt="Download Microsoft Internet Explorer" /></a> </div> </form></body></html> Важную роль в механизме передачи данных из страницы Register.aspx в страницу Header.aspx играет директива <@ Reference, которую вы видите в начале документа. Ее надо вставить вручную. Теперь обратимся к коду поддержки страницы Header.aspx. При ее открытии необходимо получить (узнать у ASP.NET) ссылку на предыдущую страницу (Register.aspx) и использовать ее для получения данных о пользователе. Как и в предыдущем случае, мы обратимся к свойству класса Page. Свойство Context дает доступ к объекту класса HttpContext, который инкапсулирует информацию о текущем состоянии страницы. В частности с помощью свойства Context мы можем получить доступ к таким (уже упоминавшимся) встроенным объектам, как: Request, Response и Server. Нас интересует доступ к интерфейсу IHttpHandler, реализованному классом HttpContext. Интерфейс IHttpHandler позволяет обрабатывать низкоуровневые запросы и ответы (request and response services) сервера IIS, но нам нужна лишь ссылка на IHttpHandler, которая добывается с помощью свойства Handler класса HttpContext. При загрузке страницы методом Server.Transfer Handler ссылается на класс, поддерживающий предыдущую страницу. На самом деле, она не считается предыдущей, так как метод Transfer проигрывает другую страницу в контексте текущей, этим он отличается от метода Response.Redirect, который честно передает управление следующей странице. Ниже показано, как пользоваться ссылкой Handler. public partial class Misc_Header : Page { protected void Page_Load (object sender, EventArgs e) { if (!IsPostBack) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { string name = ""; Misc_Register page = Context.Handler as Misc_Register; if (page != null) name = page.LoginData.name; lblGreet.Text = "Welcome " + name + '!'; } lblMsg.Text = "This course deals with Data-Centric Applications Development " + "using ADO.NET<br>Select a topic from the tree panel on the left and start learning."; } } Отметьте, что описанный способ справедлив только в случае перехода к странице тем способом, который мы рассматриваем в данный момент, а именно: Server.Transfer(nextPage);. Если переход осуществляется другим способом, например: Response.Redirect (nextPage);, то свойство Context.Handler содержит ссылку на nextPage и для передачи данных придется использовать другую технику. Введите изменения в класс Misc_Header и запустите приложение со страницы Register. Проверьте работу пары страниц (Register и Header) и правильность передачи данных. Объясните влияние атрибута style="height:100%;", который дважды использован в коде разметки страницы Header. Для этого сравните облики страниц с ним и без него. Имя класса Misc_Register, которое мы используем в коде поддержки страницы Header — это то имя, которое редактор присвоил классу в момент создания страницы. Но ASP.NET генерирует для всех страниц и компонентов User Control свои имена. Эти имена используются в том коде, который находится за сценой (во временных файлах ASP.NET). Проверьте этот факт. Для этого замените Misc_Register на ASP.misc_register_aspx и убедитесь, что страница Header работает так же, как и ранее. Просто замените строку: Misc_Register page = Context.Handler as Misc_Register; на: ASP.misc_register_aspx page = Context.Handler as ASP.misc_register_aspx; Свойство Page.PreviousPage Если передача управления между страницами происходит с помощью метода Transfer, то на принимающей стороне можно пользоваться свойством PreviousPage класса Page, которое возвращает ссылку на предыдущую страницу. Имея эту ссылку, мы можем воспользоваться методом FindControl, который возвращает ссылку на серверный элемент управления, расположенный на форме. Применим этот метод для того, чтобы добыть имя пользователя. Так как поле ввода tName существует не в контексте страницы Register.aspx (оно спрятано внутри вложенного элемента MyLogin), то нам придется два раза прибегнуть к помощи метода FindControl. Следующая весия метода Page_Load показывает, как это сделать. protected void Page_Load (object sender, EventArgs e) { if (!IsPostBack) { string name = ""; object o = Page.PreviousPage; if (o != null) { o = (o as Page).FindControl("myLogin"); if (o != null) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { o = (o as UserControl).FindControl("tName"); if (o != null) name = (o as TextBox).Text; } } lblGreet.Text = "Welcome " + name + '!'; } lblMsg.Text = @"This course deals with Data-Centric Applications Development using ADO.NET <br>Select a topic from the tree panel on the left and start learning."; } Обратите внимание на то, как используется object o. В процессе обработки он указывает (ссылается) на объекты разных типов и поэтому иногда (когда нужна конкретная функциональность) необходимо приводить типы. Сначала ссылка имеет тип Page, затем она имеет тип UserControl, и, наконец, она имеет тип TextBox, свойство Text которого мы хотим выявить. Выводы После ввода данных о пользователе в рамках страницы Register (точнее, в рамках внедренного в нее User Control) управление отдается странице Header.aspx, которая использует компоненты MyTree.ascx и MyToolbar.ascx для выбора других Web-страниц. Информация о пользователе используется в тексте приветствия другой страницы и забывается сервером. Но по предугадываемой вами логике приложения ее следует сохранить и, возможно, использовать в будущем. Например, для оповещения о новых документах сайта. Более подробно об этом сказано в статье MSDN: Passing Server Control Values Between Pages. Ссылка myLogin используется для того, чтобы задать URL следующей страницы: myLogin.NextPage = "~/Misc/Header.aspx";. Чтобы убедиться, что это присвоение выполняется до того, как переменная nextPage используется, поставьте точки останова во всех трех (2 страницы и компонент MyLogin) обработчиках события Load и запустите приложение в режиме отладке (F5). Так как элементы MyTree.ascx и MyToolbar.ascx работают совместно в рамках страницы Header, то переопределение стилей стандартных тегов в одном элементе влияет на облик тегов в другом. Эта проблема требует особого внимания. Если вы не видите проблему, то временно уберите фрагменты: style="color:White;" в MyToolbar.ascx и проанализируйте его поведение при зависании мышиного курсора над изображениями команд (тегами img). Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 3. Управление состояниями 3.1. Приложение и сессия ASP.NET-приложение (application) — это сумма файлов, страниц, модулей (modules) и DLL, которые работают в рамках данного виртуального каталога и его подкаталогов Web-сервера. Каждое приложение выполняется в своем собственном пространстве (domain), которое предотвращает конфликты имен, запрещает доступ к определенным системным ресурсам и осуществляет изоляцию статических переменных. Этим управляет класс HttpApplication. ASP.NET имеет некоторый резерв (pool) объектов класса HttpApplication, которые выделяются для обработки запросов по мере их поступления. Выделенный объект управляет запросом на протяжении всего его жизненного цикла. Затем этот объект может быть использован для обработки другого запроса. Когда происходит первое обращение к серверу, ASP.NET возбуждает событие Start (его часто называют Application_Start, по имени зарегистрированного обработчика этого события). Когда последний запрос заканчивает свой жизненный цикл, генерируется событие End или Application_End. Сессией (session) называется соединение между конкретным клиентским компьютером и Web-сервером, существующее пока пользователь обращается (send requests) к страницам данного Web-приложния. Сеанс связи обеспечивается функционирующим Web-приложением (объектом класса HttpApplication). В течение сессии также возбуждается ряд событий, главными из которых являются Session_Start и Session_End. С каждым приложением можно связать класс Global, производный от HttpApplication и управлять перечисленными событиями. Вы, возможно, знаете, что, в состав всех ASP.NET-приложений версии 1.1. по умолчанию входил файл Global.asax (active server application), в котором был определен класс Global. В версии 2.0 и выше такой файл (если он необходим) надо добавить вручную. В контекстном меню проекта выберите команду Add New Item. В окне одноименного диалога найдите строку текста Global Application Class, выберите ее и нажмите кнопку Add. Мастер студии создаст файл Global.asax, который содержит заготовки методов, реагирующих на некоторые события, возбуждаемые в определенные моменты жизни нашего приложения. Открыв его в режиме редактирования кода, вы увидите методы класса Global, который происходит от класса HttpApplication и инкапсулирует функциональность нашего Web-приложения. Как видите, в заготовку уже введены реакции на важнейшие события, происходящие с приложением в течение его жизненного цикла. Для иллюстрации переменных уровня Application state мы собираемся вставить внутрь методов класса код, который будет работать с динамической коллекцией Application, построенной по принципу хештаблицы и хранящей пары вида (имя переменной, ее значение). Для любой технологии, основанной на протоколе HTTP (Hypertext Transfer Protocol) справедливо утверждение, что HTML-документ не помнит своих состояний (is stateless), то есть он не различает, являетсяли данный запрос повторным или он пришел от другого клиента. После каждого запроса HTML-страница уничтожается и вновь создается при каждом новом запросе. Но мы уже отмечали, что ASP.NET позволяет сохранять информацию между запросами, а также обеспечивает управление состояниями на уровне сессии и приложения. Состоянием (state) называется способность Web-приложения сохранять информацию, введенную пользователем в течение сеанса связи (сессии), или в течение жизни всего приложения. Под управлением состояниями (state management) понимается механизм хранения информации в процессе обращения пользователя к различным страницам приложения. Различают два уровня (levels) управления состояниями: Application state level и Session state level. Каждое активное Web-приложение содержит ссылку на объект класса HttpApplicationState, который позволяет сохранять информацию, общую для всех пользователей. Он использует механизм управления Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 глобальной памятью Windows (global storage) и представляет собой коллекцию типа dictionary (ассоциативный массив), позволяющую хранить пары (key,value). Сами пары образуются в момент каждого запроса к определенному URL. Мы можем добавлять информацию в эту коллекцию и сервер сохранит ее между запросами. Управление осуществляется с помощью специальных переменных (application variables). Состояния, относящиеся только к данному соединению поддерживает класс HttpSessionState, объект которого вновь создается для каждого соединения. Если пользователь разорвал соединение, а затем восстановил его позже, то информация о сессии не сохраняется — создается новый объект HttpSessionState. Этот тип поддержки состояний также пользуется коллекцией типа dictionary. Управляя переменными типа session variables, можно хранить данные произвольного характера, Примером переменной типа Session state является свойство Session.SessionID. Каждая активная сессия идентифицируется уникальным 24-байтным идентификатором — текстовой строкой SessionID. Строка содержит в закодированном виде следующие данные: имя сервера, имя приложения, дата и время начала сессии. Ее генерирует сервер при создании сессии. SessionID содержит только символы ASCII (только они допустимы адресах URL). Вы можете увидеть эту уникальную строку вашего соединения, добавив в какой-либо документ aspx метку (lblSessionID), а также строку кода (в метод Page_Load): lblSessionID.Text = Session.SessionID; Рассмотрим два механизма хранения данных между запросами. Server-side. Управление состояниями на стороне сервера требует дополнительных ресурсов, но дает высокий уровень защиты данных пользователя. Client-side. Управление состояниями на стороне клиента обеспечивает минимальную защиту информации, но предоставляет быструю работу приложения, так как не требует ресурсов сервера. Следующая таблица дает представление о терминах, используемых в технологии управления состояниями. Например, информация о числе пользователей, пользующихся услугами данного приложения в данный момент, доступно всем пользователям и оно использует механизм Application state, а персональные данные о пользователе лучше хранить с помощью механизма Session state. Server-side state management Client-side state management Application state. Информация доступна всем пользователям Web-приложения. Cookies. Информация сохраняется в памяти или текстовых файлах на стороне клиента. Session state. Информация доступна только пользователю данного соединения (сессии). ViewState. Информация между запросами сохраняется в скрытой переменной HTML-документа. Database. Информация сохраняется в таблице(ах) базы данных. Query strings. Информация помещается в конец URL. Cache. Информация сохраняется в объекте Cache. Иногда для хранения больших объемов данных используется MS SQL-server или специальный state server. Этот механизм обозначается темином Database. Для его включения необходимо сначала сконфигурировать ASP.NET, манипулируя файлом global.asax. Со встроенным механизмом ViewState, использующим скрытую переменную в самом HTML-документе, а также механизмом Query strings, вы уже немного знакомы. Cookie — это небольшой сегмент данных, используемый для хранения информации о домене, клиенте и сессии. Он может быть записан в текстовый файл. 3.2. Переменные уровня сессии (session variables) В предыдущей главе был показан один из способов передачи данных о пользователе из одной страницы в другую. Здесь мы рассмотрим, как сделать это другим способом, с помощью переменных состояний уровня сессии. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Для ввода данных мы воспользуемся существующей страницей Register.aspx, в которую встроен компонент MyLogin. В данный момент он, как вы помните, запоминает данные, введенные пользователем, в структуре loginData типа UserData. Для демонстрации другого способа передачи данных внесите изменения в код метода bSubmit_Click класса Misc_MyLogin, как показано ниже. protected void bSubmit_Click (object sender, EventArgs e) { if (Page.IsValid) { loginData = new UserData(tName.Text, Convert.ToDateTime(tBirth.Text), tEmail.Text); Session["name"] = tName.Text; Session["birth"] = tBirth.Text; Session["email"] = tEmail.Text; Session["myData"] = loginData; if (!string.IsNullOrEmpty(nextPage)) Server.Transfer(nextPage); } } Теперь наряду со старым механизмом запоминания данных (поля структуры loginData) используется новый, использующий коллекцию HttpSessionState, работающую по принципу словаря (dictionary). Доступ к коллекции дает свойство Session, имеющееся в классе UserControl. Мы добавляем в словарь четыре пары. Роль ключей играют текстовые строки: "name", "birth", "email" и "myData". Им соответствуют четыре значения (value), роль которых играют строки, введенные пользователем в поля: tName, tBirth и tEmail, а также целая структура loginData. Данные коллекции сохраняются в течение определенного времени, пока пользователь не закончит сессию (сеанс связи с нашим приложением), пока он гуляет по страницам нашего приложения и не обратится к другому. Так как точно определить этот момент невозможно, то данные исчезнут по истечении 20 минут. Это определяется установкой атрибута timeout="20" тега <sessionState> файла конфигурации. Мы можем добавить этот тег в файл Web.config и изменить установку по умолчанию. Для того, чтобы воспользоваться данными, которые мы поместили в коллекцию Session, страница, принимающая данные, должна просто выбрать их. Покажем, как это сделать. В ходе обработки события Load класса Misc_Header надо, учесть альтернативу, то есть, добавить ветвь: else if (Session["myData"] != null) name = ((UserData)Session["myData"]).name; Приведем полный код новой версии метода Page_Load класса Misc_Header. protected void Page_Load (object sender, EventArgs e) { if (!IsPostBack) { string name = ""; object o = Page.PreviousPage; if (o != null) { o = (o as Page).FindControl("myLogin"); if (o != null) { o = (o as UserControl).FindControl("tName"); if (o != null) name = (o as TextBox).Text; } } else if (Session["myData"] != null) name = ((UserData)Session["myData"]).name; lblGreet.Text = "Welcome " + name + '!'; Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 } lblMsg.Text = @"This course deals with Data-Centric Applications Development using ADO.NET <br>Select a topic from the tree panel on the left and start learning."; } Ветви условного оператора выбираются в зависимости от способа, которым осуществляется переход к странице Header.aspx. Если он происходит с помощью метода Server.Transfer, то существует и работает объект Page.PreviousPage. Если переход происходит с помощью метода Response.Redirect, то работает коллекция Session. Для того, чтобы убедиться в работоспособности коллекции Session, измените способ перехода от страницы Register.aspx к странице Header.aspx. Для этого откройте файл MyLogin.ascx.cs и вместо оператора: if (!string.IsNullOrEmpty(nextPage)) Server.Transfer(nextPage); вставьте оператор: if (!string.IsNullOrEmpty(nextPage)) Response.Redirect(nextPage); Проверьте работу пары страниц Register и Header. Вы, конечно, помните, что существует альтернативное и более компактное решение, в котором вместо свойства PreviousPage используется свойство Context.Handler. В него также надо надо вставить ветвь else if (Session["myData"] . . . Как и в первом решении, ветви выбираются в зависимости от способа перехода к странице Header.aspx. Если он происходит с помощью метода Server.Transfer, то существует и работает ссылка Context.Handler, иначе работает коллекция Session. Напомню, что в .NET 2.0 появилась возможность работать с пространством имен ASP. В нем живет (определено) имя, точнее, псевдоним misc_register_aspx. Он также позволяет добыть ссылку на страницу регистрации. Возвращаясь к обзору способов передачи данных между страницами сайта, отметим, что так как коллекция Session работает в любом случае, то вы можете оставить самый простой вариант реакции на загрузку страницы Header.aspx. protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) lblGreet.Text = "Welcome " + (Session["myData"] != null ? ((UserData)Session["myData"]).name : "User") + '!'; } Сейчас коллекция Session содержит избыточное количество данных. Это сделано с целью показать возможности переменной Session. Покажем, как обратиться к другим парам коллекции Session. if (!IsPostBack) lblGreet.Text = "Hi, " + Session["name"].ToString() + "<br/>We received your birth date: " + Session["birth"].ToString() + "<br/>and email: " + Session["email"].ToString(); Запустите приложение, сделав стартовой страницу Register.aspx, и в пошаговом режиме проверьте работу всех исследованых механизмов передачи данных между страницами. 3.3. Переменные уровня приложения (application variables) Для того, чтобы показать, как работать с коллекцией данных Application, следует обратиться к файлу Global.asax. Если такого файла в проекте еще нет, то добавьте его, дав команду: Add New Item→Global Application Class. В файле Global.asax содержатся начальные заготовки методов класса Global, который происходит от класса HttpApplication и инкапсулирует функциональность нашего Web-приложения. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <%@ Application Language="C#" %> <script runat="server"> void Application_Start(object sender, EventArgs e) { // Code that runs on application startup } void Application_End(object sender, EventArgs e) { // Code that runs on application shutdown } void Application_Error(object sender, EventArgs e) { // Code that runs when an unhandled error occurs } void Session_Start(object sender, EventArgs e) { // Code that runs when a new session is started } void Session_End(object sender, EventArgs e) { // Code that runs when a session ends. // Note: The Session_End event is raised only when the sessionstate mode // is set to InProc in the Web.config file. If session mode is set to StateServer // or SQLServer, the event is not raised. } </script> Как видите, в файле есть директива <%@ Application и он представляет собой встраиваемый блок серверного кода, похожий на тот, с которым мы работали в однофайловой модели. Отмечу, что в .NET версии 1.1. он имел другую структуру. Заготовка скрипта содержит реакции на важнейшие события, происходящие с приложением в течение его жизненного цикла. Сейчас мы добавим в нее два новых метода: Application_BeginRequest и Application_EndRequest, а также код, работающий с динамической коллекцией Application. <%@ Application Language="C#" %> <%@ Import Namespace="System.Web" %> <%@ Import Namespace="System.Web.Configuration" %> <%@ Import Namespace="System.Web.SessionState" %> <script runat="server"> void Application_Start(object sender, EventArgs e) { // Создание переменной уровня приложения, или пары ("nUsers", 0) Application["nUsers"] = 0; Application["nRequests"] = 0; } void Application_BeginRequest(object sender, EventArgs e) { if (Application["nRequests"] != null) Application["nRequests"] = (int)Application["nRequests"] + 1; } void Session_Start(object sender, EventArgs e) { Response.Write("Session is Starting...<hr/>"); Application.Lock(); Application["nUsers"] = (int)Application["nUsers"] + 1; Application["nRequests"] = 0; Application.UnLock(); } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 void Application_EndRequest(object sender, EventArgs e) {} void Session_End(object sender, EventArgs e) { if (Application["nUsers"] != null && (int)Application["nUsers"] > 0) Application["nUsers"] = (int)Application["nUsers"] - 1; } void Application_End(object sender, EventArgs e) {} void Application_Error(object sender, EventArgs e) { Server.Transfer("Error.aspx"); } </script> Обработчики событий рассматриваемых уровней именуются по принципу class_event. Первым событием является событие Start класса Application, ему соответствует обработчик Application_Start. Затем события следуют в том порядке, в котором я расставил их обработчики. Полезно проверить это, расставив точки останова внутри функций обработки событий. Однако, надо иметь в виду, что сервер кэширует страницы в памяти. Если вы не внося изменений, запускаете приложение повторно, то событие Application_Start может быть пропущено. С помощью переменной Application["nUsers"], а точнее: пары вида (string, int), мы пытаемся следить за количеством клиентов, работающих с нашим приложением в данный момент. Так как работа с каждым новым клиентом ведется в рамках отдельной сессии, то счетчик числа пользователей следует увеличивать в обработчике события Session_Start. В обработчике события Session_End мы уменьшаем значение счетчика. Обрамление из вызовов двух методов Lock и UnLock можно опустить, если вы уверены, что работаете с сервером единолично. Но они необходимы в реальных ситуациях, когда множество клиентов одновременно обращаются к приложению ASP.NET. Упомянутые методы блокировки приложения осуществляют простейшую синхронизацию событий при работе в многопользовательском режиме. Одновременно с этим в обработчике Session_Start создается и обнуляется счетчик запросов данного клиента Application["nRequests"]. Увеличивать этот счетчик можно внутри обработчика Application_BeginRequest. Application["nRequests"] = (int)Application["nRequests"] + 1; Событие BeginRequest опережает событие Session_Start и при первом обращении к странице пара ("nRequests", int) внутри метода Application_BeginRequest еще не существует. Поэтому мы продублировали создание переменной в методе Application_Start. Для проверки функционирования переменных уровня приложения, следует добавить (в папку Misc) две новые страницы: TestVars.aspx и Error.aspx. Код разметки страницы TestVars.aspx выглядит следующим образом: <%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestVars.aspx.cs" Inherits="Misc_TestVars" %> <!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> <title>Test Application Variables Page</title> </head> <body onload="resizeTo(700,500);"> <form id="form1" runat="server"> <div> <asp:Label id="lblMsg" runat="server" /> <h4>Simulate an Error</h4> <asp:BulletedList id="list" BulletStyle="Disc" Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 DisplayMode="LinkButton" runat="server"> <asp:ListItem Text="403" /> <asp:ListItem Text="404" /> <asp:ListItem Text="408" /> <asp:ListItem Text="500" /> </asp:BulletedList> <asp:Button id="bSubmit" runat="server" Text="Submit" /> <asp:Button id="bRenew" runat="server" Text="Renew Session" /> </div> </form></body></html> Создайте обработчики события Click для элемента <asp:BulletedList> и кнопки bRenew. Внесите изменения в класс поддержки этой страницы, как показано ниже. using using using using System; System.Web; System.Web.UI; System.Web.UI.WebControls; public partial class Misc_TestVars : Page { protected void Page_Load(object sender, EventArgs e) { if (Session["name"] != null) Response.Write("<h2>Hi, " + Session["name"].ToString() + "!</h2><br/>"); if (Application["nUsers"] != null) Response.Write("You are the " + Application["nUsers"].ToString() + " visitor to our site."); if (Application["nRequests"] != null) Response.Write("<br/>This is your " + Application["nRequests"].ToString() + " request during this session.<br/>"); if (Session["birth"] != null) Response.Write("<br/>We received your birth date: " + Session["birth"].ToString()); if (Session["email"] != null) Response.Write(" and email: " + Session["email"].ToString()); } protected void bRenew_Click(object sender, EventArgs e) { Session.Abandon(); Response.Redirect("TestVars.aspx"); } protected void list_Click(object sender, BulletedListEventArgs e) { string text = list.Items[e.Index].Text; switch (text) { case "403": text = "Error code 403: You are not allowed to view that page"; break; case "404": text = "Error code 404: The page you requested cannot be found"; break; case "408": text = "Error code 409: The request has timed out"; break; case "500": text = "Error code 500: The server cannot fullfill your request"; break; } throw new Exception(text); } } В реакции на выбор кнопки из списка BulletedList (см. ItemsBulletedList_Click), выбрасывается исключение с тем или иным кодом. Это — имитация возникновение ошибки на сервере. При возникновении любого необработанного на сервере исключения приложение ASP.NET вызывает метод Application_Error класса Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Global. Мы позаботились о том, чтобы он, в свою очередь, передал управление странице Error.aspx. Документация MSDN говорит, что при этом важно использовать метод Server.Transfer("Error.aspx");. Теперь добавьте страницу Error.aspx. Код разметки можно оставить без изменения. Код поддержки страницы Error.aspx приведен ниже, он вылавливает выброшенное нами исключение (имитацию) и выводит его причину. using System; using System.Web; using System.Web.UI; public partial class Misc_Error : Page { protected void Page_Load(object sender, EventArgs e) { Exception ex = Server.GetLastError(); if (ex != null) { ex = ex.InnerException; if (ex != null) Response.Write(string.Format("<h3>{0}</h3>", ex.Message)); } Server.ClearError(); } } Запустите проект со страницы TestVars.aspx и, нажимая кнопки Submit и Renew Session, попытайтесь объяснить выводимую информацию. Затем проверьте работу списка гиперссылок, имитирующих сбои, или ошибки сервера. Рис. 3.1. Реакция на события класса Global Тонким местом в реализации Misc_Error является то, ASP.NET умудрилась обернуть выброшенное нами исключение другим (думаю, что здесь не обошлось без вмешательства системы Security). В связи с этим приходится вынимать наше исключение таким образом: ex = ex.InnerException; Это обстоятельство не отмечено в документации (см. MSDN: How to: Handle Application-Level Errors), поэтому без последней операции вы не увидите истинную причину исключения. После вывода сообщения об ошибке Microsoft рекомендует сбрасывать ее таким образом: Server.ClearError();. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 При каждом нажатии кнопки Submit счетчик запросов увеличивается на единицу, так как каждое новое обновление увеличивает значение (value) пары ("nRequests", value). При этом счетчик числа пользователей nUsers будет оставаться равным 1. Обновление браузера (F5) приводит к тому же результату. Нажатие кнопки Renew Session приводит к выполнению такого кода: Session.Abandon(); Response.Redirect("TestVars.aspx"); Первый оператор вызывает метод Abandon класса HttpSessionState. Он прерывает текущюю сессию (делает ее invalid) и возбуждает событие End, реакция на которое (Session_End) определена в нашей версии Webприложения (то есть в классе Global). В ходе обработки события End счетчик числа пользователей уменьшается, но он тут же снова увеличивается, так как мы вновь открываем страницу. Внутри метода Session_End бесполезно что-либо выводить в окно браузера, так как вслед за событием End сразу произойдет событие Start (см. вызов Response.Redirect("TestVars.aspx");). Оно вновь запустит страницу TestVars.aspx. Теперь закройте браузер, вновь запустите приложение. Что покажет страница? Счетчик числа пользователей увеличится, несмотря на тот факт, что сеанс закончился, да и пользователь остался тем же. Как это объяснить? Ответ: событие Session_End не возбуждается. Это результат кэширования. Дайте команду Build→Rebuild Solution и вновь запустите приложение. Счетчик числа пользователей вновь станет равен единице. Как это объяснить? Кэширование не работает, так как обновлена DLL. Попробуйте предсказать поведение счетчиков при запуске второго экземпляра браузера и проверьте ваше предсказание. Выберите строку списка, симулирующую возникновение ошибки с заданным кодом. Приложение осуществляет переход к странице Error.aspx, которая выводит описание причины исключения. Рис. 3.2. Симуляция возникновения ошибки с заданным кодом Чтобы увидеть реакцию системы, предусмотренную по умолчанию, закомментируйте содержимое метода Application_Error. Вспомните комментарий, который был в теле заготовки метода Session_End. Он предупреждал, что событие Session_End возбуждается только в случае, когда атрибут mode элемента <sessionState> файла конфигурации Web.config установлен в значение "InProc". Документация (см. Index→sessionState element) сообщает, что для элемента <sessionState> по умолчанию действует установка, приведенная ниже. <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" stateNetworkTimeout="10" sqlConnectionString="data source=127.0.0.1;Integrated Security=SSPI" sqlCommandTimeout="30" customProvider="" cookieless="UseCookies" Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 cookieName="ASP.NET_SessionId" timeout="20" allowCustomSqlDatabase="false" regenerateExpiredSessionId="true" partitionResolverType="" useHostingIdentity="true"> <providers> <clear /> </providers> </sessionState> Как видите, атрибут mode имеет нужное значение. Для убедительности вы можете вставить этот фрагмент внутрь элемента <configuration><system.web> файла Web.config и повторить опыт. Он вновь покажет, что метод Session_End не вызывается при завершении сеанса работы с клиентом (закрытии браузера). Весьма возможно, что по истечении 20 минут (см. установку timeout="20", которая действует по умолчанию) это событие произойдет, но это стоит проверить. 3.4. Одновременная работа с коллекциями Application и Session Для того, чтобы одновременно задействовать переменные уровня приложения и уровня сессии, внесем небольшие изменения в облик и поведение страницы Register.aspx. В начало страницы Register.aspx (перед объявлением панели <div>) вставьте флажок, который позволит клиенту выбрать (в качестве продолжения сессии) одну из двух страниц сайта: Header.aspx (как было ранее) или TestVars.aspx. <asp:CheckBox ID="ch" runat="server" Text="Test Application Variables" /><br/><br/> Измените логику выбора следующей страницы. Для этого в метод Page_Load класса Misc_Register внесите исправление. myLogin.NextPage = ch.Checked ? "~/Misc/TestVars.aspx" : "~/Misc/Header.aspx"; Запустите страницу Register.aspx. Убедитесь, что появилось сообщение о начале сессии, а также флажок, позволяющий осуществить переход к странице TestVars.aspx. Установите флажок и нажмите кнопку Submit. Управление будет передано странице TestVars.aspx, которая выводит значения переменных уровня приложения, объявленных в классе Global. Кроме того, страница покажет значения переменных уровня сессии, которые были созданы и инициализированы в методе Page_Load класса Misc_MyLogin. Попытайтесь объяснить показания счетчиков при нажатии кнопок Назад, Submit и Renew Session. Достаточно сложно объяснить последовательность событий и вызываемых ими изменений переменных уровня приложения при работе с парой страниц Register.aspx и Header.aspx. Счетчик числа запросов растет значительно быстрее, чем ожидалось. При первоначальной загрузке событие BeginRequest возбуждается для каждой из гиперссылок, расположенных в дереве. Оно возникает также и при наведении курсора мыши на любую из этих гиперссылок. Более простой сценарий Рассмотрим более простой и предсказуемый сценарий работы с коллекцией Application. Добавьте в папку Misc новую страницу с именем TestEvents.aspx. Внесите изменения, как показано ниже, и проверьте ее работу. Приведем код разметки страницы TestEvents.aspx. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestEvents.aspx.cs" Inherits="Misc_TestEvents" %> <!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" > Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <head> <title>TestEvents Page</title> <style type="text/css"> .text { font-family:Comic Sans MS; vertical-align:middle; } .green { text-align:center; background-color:Black; font-family:Comic Sans MS; font-weight:bold; color:Lime; vertical-align:middle; font-size:larger; } </style> </head> <body><form id="myForm" runat="server"><div> <asp:Label ID="msg1" runat="server" CssClass="text"></asp:Label> <input type="text" class="green" readonly="readonly" id="sVisits" runat="server" size="3" /> <asp:Label ID="msg2" runat="server" CssClass="text"></asp:Label> <input type="text" class="green" readonly="readonly" id="sRequests" runat="server" size="3" /><br/> <asp:Label ID="msg3" runat="server" CssClass="text"></asp:Label> <br /><br /> <input type="submit" value="Submit" /> </div></form></body> </html> Код поддержки страницы TestEvents.aspx приведен ниже. Заметьте, что значения меток обновляются при каждой загрузке документа на стороне сервера (в методе Page_Load). using System; using System.Web.UI; public partial class Misc_TestEvents : Page { protected void Page_Load (object sender, EventArgs e) { string s = "User of " + Request.Url.ToString(); msg1.Text = "Hi, " + s + "!<br /><br />You are the visitor number: "; sVisits.Value = Application["nUsers"].ToString(); msg2.Text = "since the Application started.<br />" + "During this session you requested us: "; sRequests.Value = Application["nRequests"].ToString(); msg3.Text = "Press Submit or F5 to send a next request"; } } Для прояснения картины событий внесите несколько изменений в файл Global.asax. Внутрь метода Application_BeginRequest вставьте следующий код. Response.Write("<h4>Using class Global</h4>Request is beginning...<br/>"); Внутрь метода Application_EndRequest вставьте код. Response.Write("<br/><hr/>Request is Ending..."); Добавьте метод, реагирующий на событие AuthenticateRequest. void Application_AuthenticateRequest(object sender, EventArgs e) { Response.Write("Application is Authenticating Request...<br/>"); } На рис. 3.3 приведен результат функционирования страницы TestEvents и вы видите, что при первом запуске счетчик запросов равен нулю. Объясните показания счетчиков при нажатии кнопок Submit, F5, закрытии и повторном открытии браузера, запуске страницы после закрытия студии и повторного открытия, а также при одновременном запуске двух экземпляров браузера. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 3.3. Подсчет числа запросов и числа пользователей 3.5. События класса Global Генерируемая студией начальная заготовка класса Global содержит обработчики только самых необходимых событий HttpApplication. Существуют и другие события, обработку которых вы можете добавить в своей версии Global.asax. Следующая таблица перечисляет события, связанные с запросом страницы. Имя функции обработки Описание Application_BeginRequest Генерируется в момент получения запроса Application_AuthenticateRequest Указывает, что запрос готов к аутентификации Application_AuthorizeRequest Указывает, что запрос готов к авторизации Application_ResolveRequestCache Модуль кэширования сообщает, что обработку запроса надо прекратить, так как документ есть в кэше Application_AcquireRequestState Сигнализирует о том, конкретный запрос Application_PreRequestHandlerExecute Сигнализирует о том, что обработчик запроса готов к выполнению что необходимо получить состояние, описывающее Еще одна таблица перечисляет события, связанные с отправкой клиенту запрошенной страницы В этот момент главным действующим лицом становится встроенный объект Response. Имя функции обработки Описание Application_PostRequestHandlerExecute Генерируется после того, как handler запроса (ASP.NET page или Web service) завершил свою работу Application_ReleaseRequestState Генерируется, когда состояние запроса надо сохранить, так как ASP.NETприложения завершается Application_UpdateRequestCache Сигнализирует, что обработка кода завершилось и файл надо добавить в кэш Application_EndRequest Последнее событие. Оно возникает, когда ASP.NET-приложения завершается. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Следующая таблица перечисляет знакомые вам события — conditional events. В процессе обработки запроса они возбуждаются не всегда, а лишь при выполнении определенных условий. Функция обработки Описание Application_Start Генерируется в момент старта ASP.NET-приложения Application_End Генерируется в момент завершения Session_Start Генерируется в момент начала сеанса связи (сессии) с пользователем Session_End Генерируется в момент завершения сеанса связи (сессии) с пользователем Application_Error Генерируется при возникновении ошибки Следующая схема иллюстрирует события с учетом таких участников, как: IIS, клиент и ядро ASP.NET. Напомним, что сами события обрабатываются в классе HttpApplication, а точнее, в производном от него классе Global. Рис. 3.4. Последовательность событий уровня приложния Время жизни переменных уровней Application и Session Переменные состояний отнимают у сервера определенные ресурсы, поэтому важно освобождать их в тот момент, когда пользователь покинул сайт. Но Web-сервер не может определить, когда пользователь покинул сайт, так как протокол HTTP не помнит состояний. Для экономии ресурсов сервер использует механизм, основанный на завершении лимита времени, в течение которого пользователь не делает или не обновляет запрос. Если этот лимит закончился, то Web-сервер освобождает ресурсы, связанные с текущей сессией. Лимит времени задан значеним атрибута timeout элемента sessionState. Этот тег по умолчанию отсутствует в XML-документе Web.config, но вы можете вставить его, как было показано выше. По умолчанию, значение атрибута timeout равно 20. Это означает, что если в течение 20 минут пользователь не проявил активности, то Web-сервер завершает сессию и удаляет все переменные в коллекции session state. Если после этого пользователь вновь обратится к серверу, то он будет расцениваться как новый пользователь. С помощью атрибута timeout вы можете управлять лимитом времени, влияя тем самым на эффективность использования ресурсов и время отклика. 3.6. Управление состояниями на стороне клиента Для управления состояниями на стороне клиента большинство Web-приложений использует cookies — небольшой сегмент данных о странице, который сервер посылает клиенту вместе с кодом страницы. Записи Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 cookies впервые создаются сервером, а затем они путешествуют туда-сюда, пока не закончится сеанс связи. После этого cookies хранятся какое-то время на стороне клиента. Вы можете узнать происхождение (etymology) термина cookies в The New Hacker's Dictionary. (http://www.catb.org/~esr/jargon) Например, SessionID передается между клиентом и сервером либо посредством механизма cookie, либо с помощью модификации самого URL (так называемый cookieless SessionID). Способ передачи зависит от конфигурации Web-приложения, которая производится с помощью файла global.asax. Cookies используются для хранения данных самого разного характера. В основном они помогают безликому автомату, которым является HTTP-сервер, запомнить клиента и, например, не заставлять его вновь вводить данные регистрации на сайте или не просить его участвовать в опросе, если ранее он уже заполнил форму. Записи cookies в течение сессии либо хранятся в текстовом файле, либо остаются в памяти. Серверное приложение использует их для хранения информации о клиенте, сессии или состоянии приложения. При повторных запросах к серверу браузер отправляет cookies вместе с данными запроса. При обработке запроса сервер выделяет и использует данные cookies. В этот момент они уже содержат дополнительные данные о домене и клиенте. Различают два типа cookies: Temporary. Временные cookies, которые также называются session-cookies или неустойчивые (nonpersistent) cookies. Они существуют в памяти браузера и теряются при его выключении. Persistent. Устойчивые cookies имеют определенный срок годности (expiration period). Когда браузер работает со страницей, которая использует устойчивые cookies, то он сохраняет их на диске своей (клиентской) машины. Вы знаете, что Internet Explorer хранит свои cookies в отдельной папке с таким же именем. Файл обычно получает имя username@domainname.txt. Клиент в любой момент может удалить все устойчивые cookies. Это надо иметь в виду при разработке Webприложений. Порядок удаления cookies, срок годности которых истек, определяется системными правилами (expiration rules). Для файлов cookies существует ограничение по памяти (4K). Некоторые браузеры ограничивают количество записей, поступивших от одного сайта (от 20 до 300). Записи Cookies ассоциированы с Web-сайтом, а не с конкретной страницей. Посещая различные сайты браузер обрастает множеством Cookies. Открытость файлов cookies считается слабым звеном в безопасности системы. Пример использования Cookies Для освоения принципов работы с коллекциями Cookies создайте 2 новых страницы: CookieMaker и CookieUser. Сначала удалите или закомментируйте все строки кода Response.Write из обработчиков событий класса Global. Лишний текст будет нас отвлекать. Вставьте в стартовый документ CookieUser следующий код разметки. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CookieUser.aspx.cs" Inherits="Misc_CookieUser" %> <!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><title>CookieUser Page</title></head> <body><form id="form1" runat="server"> <div style="padding:5px; border: 1px solid;" runat="server" id="myDiv"> <h3 style="text-align: center;">Cookie</h3> Storing cookies on the client is one of the methods that ASP.NET's session state uses to associate requests with sessions. Cookies can also be used directly to persist data between requests, but the data is then stored on the client and sent to the server with every request. Browsers place limits on the size of a cookie; therefore, only a maximum of 4096 bytes is guaranteed to be acceptable. <br /><br /><b><a runat="server" id="myAnchor" href="CookieMaker.aspx"> Customize This Page</a></b> </div> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 </form></body></html> Здесь размещен простой текст, стилевые свойства которого мы собираемся изменять с помощью элементов и логики страницы CookieMaker. Переход к странице CookieMaker осуществляется с помощью простой гиперссылки. Изменения, произведенные пользователем в рамках страницы CookieMaker, мы сохраним, а потом и восстановим, с помощью механизма cookies. Запустите страницу CookieUser и просмотрите выводимый ею текст. Коллекция заголовков Перед тем как создать коллекцию записей cookies, познакомимся с другой коллекцией, Headers. Она всегда используется при передаче информации между страницами приложения. Коллекцией HTTP-заголовков управляет класс HttpRequest, а свойство Headers дает к ней доступ. Headers является коллекцией уже знакомого вам типа NameValueCollection. Напомним, что коллекции такого типа содержат пары вида (string, string[ ]). MSDN определяет это так: " NameValueCollection stores multiple string values under a single key". Рис. 3.5. Страница, использующая данные коллекции Cookies Коллекция Headers нужна нам для того, чтобы обнаружить полезную запись Referer. Откройте файл с кодом поддержки первой страницы (CookieUser) и введите изменения в класс Misc_CookieUser. public partial class Misc_CookieUser : Page { protected void Page_Load (object sender, EventArgs e) { ShowHeaders (); } void ShowHeaders () { string[] headers = Request.Headers.AllKeys; // Коллекция ключей // Хотим вывести данные в аккуратной таблице Response.Write("<style>th {text-align: left; background: #aaaaaa;" + Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 "font-family:'MS Sans Serif'; padding-left: 1ex; font-size:8pt;}" + "td {border-bottom: 1 solid #CCCCCC; font-family: MS Sans Serif; " + "font-size:8pt; padding-left:1ex;} </style>" + "<table><tr><th>Key</th><th>Value</th></tr>"); foreach (string h in headers) { Response.Write("<tr><td>" + h + "</td>"); string[] vals = Request.Headers.GetValues(h); foreach (string v in vals) Response.Write("<td>" + v + "</td>"); Response.Write("</tr>"); } } Запустите страницу CookieUser и просмотрите выведенную в таблице коллекцию заголовков. Мы не будем рассматривать конкретные пары коллекции, но сравним ее с теми значениями, которые установятся при переходе на вторую страницу. На рис. 3.6 приведен вид окна браузера, отображающего таблицу заголовков Headers. Свойство Referer и коллекция ViewState Откройте файл поддержки страницы второй страницы (CookieMaker), Скопируйте все содержимое класса Misc_CookieUser и вставьте его в класс Misc_CookieMaker. Внимание. Не трогайте заголовок класса, замените только его содержимое. Вновь запустите страницу CookieUser, нажмите на гиперссылку Customize This Page. Рассмотрите содержимое коллекции Headers при загрузке второй страницы (CookieMaker). Рис. 3.6. Страница, генерирующая данные коллекции Cookies Сравните обе таблицы и обратите внимание на две пары, которые появились в новой таблице: ("Cookie", ее значение) и ("Referer", "адрес предыдущей страницы"). Первая пара содержит идентификатор сессии и уже иллюстрирует способ передачи коллекции cookies. Вторая пара представляет особый интерес для разработчика Web-приложений, так как позволяет узнать адрес той страницы, которая сослалась на данную (см. свойство Referer). Далее, работая с двумя нашими страницами, мы намерены использовать значение этого свойства для организации переходов туда-сюда. Опишем сценарий работы с двумя страницами CookieUser и CookieMaker. Первая страница CookieUser отображает текст, который вы уже видели. Далее гиперссылка передает управление странице CookieMaker. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 После коррекции данных в рамках страницы CookieMaker, пользователь нажимает кнопку Submit, и вновь переходит к странице CookieUser. Для запоминания адреса предыдущей страницы используем еще одну коллекцию. Она имеет тип ViewState. Каждый серверный элемент управления использует эту коллекцию для хранения своих состояний и мы об этом уже не раз говорили. Состояния, как вы помните, тоже представляют собой пары (key, value). ViewState — это свойство класса Control, которое дает доступ к объекту класса StateBag. Последний предназначен для хранения пар вида (string, string). Первая строка, обычно играет роль атрибута, а вторая — его значения. Как было показано ранее, коллекция состояний передается в виде скрытого (hidden) элемента типа input. Иногда эту коллекцию используют для хранения других, произвольных строковых пар. Именно это мы и собираемся сделать. При загрузке страницы CookieMaker мы запомним в коллекции ViewState адрес страницы CookieUser. Это выполнит такая строка кода: ViewState["Referer"] = Request.Headers["Referer"]; Перенаправить сервер на предыдущую страницу можно с помощью метода Redirect класса HttpResponse. Заметим, что техника добывания адреса предыдущей страницы понадобится вам во многих других сценариях. Вопрос. Почему мы прибегаем к помощи коллекции ViewState? Разве нельзя использовать для этой цели коллекцию заголовков (Request.Headers)? Для ответа на этот вопрос либо проведите мысленный эксперимент, либо добавьте в страницу CookieMaker кнопку Submit. Ее нажатие провоцирует обмен с сервером. В момент нажатия этой кнопки предыдущей страницей будет . . . (закончите фразу). Если решить эту задачу мысленно не удается, то поставьте точку останова в обработчике события Click кнопки Submit и сравните (с помощью инструмента Quick Watch) значения двух переменных: Request.Headers["Referer"] и ViewState["Referer"]. Ответ При нажатии кнопки Submit осуществляется уход со страницы CookieMaker (то есть, обычный postback). В переменной Request.Headers["Referer"] в этот момент содержится адрес страницы CookieMaker (той страницы, из которой мы уходим). В коллекции ViewState["Referer"] в этот момент содержится то, что мы туда записали ранее (если вообще что-нибудь записали). По нашему замыслу там должен быть адрес страницы CookieUser. Механизм работы Cookies Возвратимся к нашей задаче — иллюстрации механизма cookie. Один объект класса HttpCookie инкапсулирует функциональность хорошо известной вам коллекции NameValueCollection. Она способна сопоставить с одним ключом типа string несколько значений (тоже типа string). Коллекция объектов класса HttpCookie (по сути это уже коллекция коллекций) представляет собой новый тип HttpCookieCollection. Этот класс происходит от класса NameObjectCollectionBase, имя которого намекает на то, что он представляет собой специализированную коллекцию пар (name, object), где роль ключа выполняет строка текста, а значения — объект произвольного типа. Каждая коллекция HttpCookie имеет свое имя (name). С его помощью можно выбрать коллекцию (object) из коллекции коллекций. Доступ к коллекции объектов HttpCookie получаем с помощью свойства Cookies класса HttpRequest. Точно таким же свойством обладает и класс HttpResponse. Зачем нужно иметь много коллекций типа HttpCookie? Это удобно. Например, мы собираемся создать коллекцию с именем myCookies. Для выбора ее из множества коллекций следует использовать такой синтаксис: Request.Cookies["myCookies"]. Мы могли бы создать еще одну Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 коллекцию с именем colors и выбрать ее так: Request.Cookies["colors"]. Имея ключ key какой-то одной записи в коллекции colors, мы можем достать ее значение таким образом: Request.Cookies["colors"].Values[key]; // Обращение к коллекции коллекций ASP.NET поддерживает две сложные коллекции HttpCookieCollection. Каждая из них — это коллекция коллекций пар типа (string, string[]). Коллекция Response.Cookies содержит записи, созданные на стороне сервера и передаваемые клиенту в заголовке с ключом Set-Cookie. Коллекция Request.Cookies содержит записи, переданные клиентом в заголовке с ключом Cookie. Response.Cookies можно рассматривать как письмо сервера, направляемое браузеру. Браузер запоминает это письмо. Когда он вновь собирается послать запрос серверу, то смотрит нет-ли записей, предназначенных данному сайту в коллекции Cookies. Если есть (и срок годности их не исчерпан), то он посылает их серверу вместе с запросом (это Request.Cookies). Нам важно четко сформулировать задачу и не запутаться в коллекциях, так как их достаточно много. Страница CookieMaker один раз создает коллекцию Response.Cookies, а далее при каждой загрузке опрашивает коллекцию Request.Cookies и использует ее данные для синхронизации своих элементов управления. Страница CookieUser при каждой загрузке опрашивает коллекцию Request.Cookies для изменения облика своих элементов. Если Response.Cookies отсутствует (это справедливо при первом запуске), то страница CookieMaker должна создать ее и передать странице CookieUser. Последняя использует эти данные для коррекции атрибутов стиля своего текста. Далее страница CookieMaker корректирует уже существующую коллекцию Response.Cookies и вновь возвращает ее странице CookieUser. Цель этого примера — понять суть механизма Cookies и технику его использования для передачи данных между двумя страницами. С учетом сказанного, я надеюсь, будет проще воспринять код страниц и код классов их поддержки. Рассмотрим изменения в странице CookieUser. На ней присутствуют два элемента, которые имеют атрибут runat="server": панель <div id="myDiv"> и гиперссылка <a id="myAnchor">. Это дает нам возможность управлять их атрибутами на стороне сервера. Мы собираемся делать это в ходе загрузки документа. В связи с этим файл с кодом разметки остается без изменений. Рассмотрим код класса Misc_CookieUser. public partial class Misc_CookieUser : Page { void Page_Load(object sender, EventArgs e) { ShowHeaders(); myDiv.Style["background-color"] = GetStyle("bkColor"); myDiv.Style["font-family"] = GetStyle("fontName"); myDiv.Style["font-size"] = GetStyle("fontSize"); myAnchor.Style["color"] = GetStyle("lkColor"); } void ShowHeaders() { string[] headers = Request.Headers.AllKeys; // Коллекция ключей // Хотим вывести данные в аккуратной таблице Response.Write("<style>th {text-align: left; background: #aaaaaa;" + "font-family:'MS Sans Serif'; padding-left: 1ex; font-size:8pt;}" + "td {border-bottom: 1 solid #CCCCCC; font-family: MS Sans Serif; " + "font-size:8pt; padding-left:1ex;} </style>" + "<table><tr><th>Key</th><th>Value</th></tr>"); foreach (string h in headers) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { Response.Write("<tr><td>" + h + "</td>"); string[] vals = Request.Headers.GetValues(h); foreach (string v in vals) Response.Write("<td>" + v + "</td>"); Response.Write("</tr>"); } Response.Write("</table>"); } //==== Этот метод используется в inline-скрипте (см. код разметки) protected string GetStyle (string key) // Выбор записи по ключу { HttpCookie cookie = Request.Cookies["myCookies"]; return cookie != null ? cookie.Values[key] : ""; } } Новый метод GetStyle обращается к коллекции Request. Cookies["myCookies"], и выбирает из нее запись по ключу, заданному параметром метода. Таким образом он позволяет выбрать из именованной коллекции "myCookies" любую из ее записей. Обращение к методу GetStyle производится в ходе выполнения метода Page_Load. Здесь мы по очереди выбираем все записи из коллекции myCookies и применяем выбранные строки текста для коррекции стиля панели и гиперссылки. Например. myDiv.Style["background-color"] = GetStyle("bkColor"); Теперь рассмотрим код страницы CookieMaker. C ее помощью клиент сможет изменить записи Cookies и, тем самым изменить облик страницы CookieUser. Вот код разметки страницы. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CookieMaker.aspx.cs" Inherits="Misc_CookieMaker" %> <!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><title>CookieMaker Page</title></head> <body><form id="form1" runat="server"> <div style="font-family:Verdana;font-size:smaller; border:dodgerblue ridge;padding:10px; left:8px;width:270px;position:absolute;top:260px;height:184px; background-color:InfoBackground;"><br /> <asp:Label ID="bk" runat="server" Text="Background:" Width="100"/> <asp:DropDownList ID="bkColor" runat="server" Width="130"/><br /> <asp:Label ID="lk" runat="server" Text="Link Color:" Width="100"/> <asp:DropDownList ID="lkColor" runat="server" Width="130"/><br /> <asp:Label ID="fn" runat="server" Text="FontName:" Width="100"/> <asp:DropDownList ID="fontName" runat="server" Width="130"/><br /> <asp:Label ID="fs" runat="server" Text="FontSize:" Width="100"/> <asp:DropDownList ID="fontSize" runat="server" Width="130"/><br /><br /> <input id="bSubmit" type="submit" value="Submit" runat="server" onserverclick="bSubmit_ServerClick" /> </div></form></body></html> На форме расположены 4 выпадающих списка (DropDownList) для выбора 4 компонентов стиля. Алгоритм работы страницы реализован в коде поддержки CookieMaker.aspx.cs. Рассмотрим его. public partial class Misc_CookieMaker : Page { static Dictionary<string, string[]> attrs; // Словарь соответствий HttpCookie cookie; // Коллекция пар (string, string[]) protected override void OnInit(EventArgs e) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 { base.OnInit(e); attrs = new Dictionary<string, string[]>(4); attrs.Add(bkColor.ID, new string[] { "LightBlue", "LightCoral", "Lime", "White", "Wheat" }); attrs.Add(lkColor.ID, new string[] { "Blue", "DarkGreen", "DarkOrchid", "DodgerBlu", "Chocolate" }); attrs.Add(fontName.ID, new string[] { "Verdana", "Arial", "Times New Roman", "Comic Sans MS","Garamond","Courier New" }); attrs.Add(fontSize.ID, new string[] { "8pt","9pt","10pt","11pt","12pt","14pt" }); } void Page_Load(object sender, EventArgs e) { ShowHeaders(); if (!IsPostBack) { //===== Запоминаем страницу, которая передала нам управление //===== (В нашем случае — это CookieUser) ViewState["Referer"] = Request.Headers["Referer"]; SetCookie(); } } void ShowHeaders() { string[] headers = Request.Headers.AllKeys; // Ключи коллекции заголовков //======= Хотим вывести данные в аккуратной таблице Response.Write("<style>th {text-align: left; background: #aaaaaa;" + "font-family:'MS Sans Serif'; padding-left: 1ex; font-size:8pt;}" + "td {border-bottom: 1 solid #CCCCCC; font-family: MS Sans Serif; " + "font-size:8pt; padding-left:1ex;} </style>" + "<table><tr><th>Key</th><th>Value</th></tr>"); foreach (string h in headers) { Response.Write("<tr><td>" + h + "</td>"); string[] vals = Request.Headers.GetValues(h); foreach (string v in vals) Response.Write("<td>" + v + "</td>"); Response.Write("</tr>"); } Response.Write("</table>"); } void SetCookie() { cookie = Request.Cookies["myCookies"]; // Выбор одной коллекции (myCookies) bool noCookie = cookie == null; if (noCookie) cookie = new HttpCookie("myCookies"); foreach (KeyValuePair<string, string[]> pair in attrs) { DropDownList list = FindControl(pair.Key) as DropDownList; foreach (string s in pair.Value) list.Items.Add(new ListItem(s)); if (noCookie) cookie.Values.Add (pair.Key, list.Text); else list.SelectedValue = cookie.Values[list.ID]; } Response.Cookies.Add (cookie); // Вставляем cookie в коллекцию коллекций Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 } public void bSubmit_ServerClick(object sender, EventArgs e) { cookie = Response.Cookies["myCookies"]; if (attrs != null) { foreach (KeyValuePair<string, string[]> pair in attrs) { DropDownList list = FindControl(pair.Key) as DropDownList; cookie.Values[list.ID] = list.Text; } } string address = ViewState["Referer"] as string; if (address == null) address = "CookieUser.aspx"; Response.Redirect(address); // Передаем управление предыдущей странице } } Методы класса CookieMaker и создают, опрашивают и корректируют записи Cookies. В классе объявлена статическая переменная attrs, которая представляет собой типизированнаю (Generic) коллекцию пар. static Dictionary<string, string[]> attrs; В ней мы собираемся хранить все множество пар, или соответствий вида (имя стиля, его значение). Этот словарь является источником для Cookies и ее прототипом, в том смысле, что он тоже является коллекцией именованных коллекций. Так как словарь имеет описатель static, то его заполнение целесообразно выполнить в методе OnInit, то есть, до загрузки страницы на сервере. По этой же причине (static), заполнение производится один раз в жизненном цикле приложения. В качестве ключей коллекции myCookies выбрано множество из 4-х стилей текста {"bkColor", "lkColor", "fontName", "fontSize"}. Они соответствуют идентификаторам четырех выпадающих списков и выбираются из кода разметки (см. код bkColor.ID, и т. д.) . В качестве значений (они должны иметь тип string[]) заданы строки текста (имена цветов, и т. д.). При загрузке страницы (см. Page_Load) мы запоминаем страницу, которая передала нам управление, и вызываем метод SetCookie, который (если необходимо) создает коллекцию HttpCookie по имени "myCookies", заполняет ее данными из словаря и помещает в коллекцию Response.Cookies. Вы помните, что эта коллекция коллекций имеет тип HttpCookieCollection. Изменение данных Response.Cookies["myCookies"] производится при завершении диалога изменения свойств (то есть, при обработке события ServerClick кнопки bSubmit). В этот же момент производится переадресация. В браузер посылается другая страница (CookieUser). string address = ViewState["Referer"] as string; if (address == null) address = "CookieUser.aspx"; Response.Redirect(address); // Передаем управление предыдущей странице На рис. 3.7 вы видите страницу CookieMaker после того, как пользователь внес изменения в исходные данные и вновь вернулся для их коррекции. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 3.7. Диалог изменения данных коллекции Cookies Стандартные списки DropDownList позволяют выбрать компоненты стиля. После принятия (Submit) изменений управление передается странице CookieUser, которая должна отобразить изменения, сохраненные в Cookies. Response и Request Cookies Важно понять механизм передачи Cookies между двумя страницами. Для упрощения этой задачи установите точки останова в методах Page_Load обоих классов, а также в обработчике нажатия кнопки Submit и пройдите пару раз весь путь в режиме отладки. Результат коррекции запоминается в коллекции Response.Cookies. При переходе к другой странице, и выборе данных эта же коллекция уже носит имя Request.Cookies, при возврате назад она является Response.Cookies и т. д. Запустите и проверьте работу пары страниц. Внимательно рассмотрите строку заголовка с ключом Cookie. Она отображает состояние коллекции Cookies, в которой имеются два объекта HttpCookie, каждый из которых является коллекцией типа NameValueCollection. Коллекции разделены между собой символом ;. Первой коллекции соответствует ключ в виде текстовой строки: ASP.NET_SessionId. В нее входит только одна запись — уникальный идентификатор сессии, обсуждавшийся ранее. Вторая коллекция задана ключом: myCookies (наша коллекция). В нее входят пары типа (атрибут, значение). Например, bkColor=Lime. Отдельные записи коллекции разделены символом &. Выводы С помощью метода ShowHeaders и усилий, затраченных на создание таблицы, отображающей содержимое коллекции заголопков (Request.Headers) нам удалось показать внутреннюю кухню механизма cookies. Теперь можно убрать метод ShowHeaders (а также его вызовы) из обоих классов (однако, сам прием может оказаться полезным когда-нибудь в будущем) Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Приложение пользуется услугами коллекции cookies для управления параметрами стиля. Еще раз повторим, что результаты коррекции стилей передаются с помощью только одной из множества коллекций Cookies (ее ключ "myCookies"). Время жизни Cookies Мы уже говорили о том, что записи cookies бывают временными (session-cookies) и более устойчивами (persistent cookies). По умолчанию записи являются временными. Для того, чтобы временные cookies превратить в устойчивые достаточно добавить такой код. cookie.Expires = DateTime.Now.AddHours(1); Этот код сообщает ядру ASP.NET, что срок действия коллекции коллекций записей истечет через час. В течение этого срока коллекция cookie будет храниться на диске клиента или на диске сервера (в зависимости от того, кто активен в данный момент). Временные (temporary) записи cookie уничтожаются при выходе из браузера. Если включен режим устойчивости cookies и пользователь закроет браузер, а затем вновь откроет до того, как истек срок действия cookies, то механизм cookies будет работать. Вы помните, также что клиент в любой момент может удалить все устойчивые cookies. Вот пример создания новой коллекции, которая будет сохранена на диске и отправлена браузеру, который также будет хранить ее в течение одного дня. HttpCookie cookie = new HttpCookie ("lastVisit"); cookie.Value = DateTime.Now.ToString(); cookie.Expires = DateTime.Now.AddDays(1); Response.Cookies.Add (cookie); А вот пример, в котором элемент данных меняет свое значение. Одновременно изменяется срок годности коллекции с именем userName. Response.Cookies["userName"].Value = "Mike" Response.Cookies["userName"].Expires = DateTime.Now.AddDays(1); Работа в режиме Cookieless Session Каждая сессия идентифицируется строкой SessionID, которая по умолчанию передается с помощью cookie. Если клиент настроил свой браузер так, что он игнорирует механизм cookie, то данные старой сессии теряются и каждое обращение к серверу открывает новую сессию. ASP.NET имеет настройку, которая позволяет в указанной ситуации запоминать SessionID и работать в режиме cookieless session. При первом обращении к серверу с такой настройкой URL клиента автоматически изменяется так, чтобы в него встроился идентификатор сессии. Например, адрес http://server//page.aspx заменяется на: http://server/(h44a1e55c0breu552yrecobl)/page.aspx Часть в скобках является строкой SessionID, идентифицирующей текущего пользователя. Она используется сервером в течение всей сессии. При этом возникает ограничение. Ссылки на другие страницы не могут содержать абсолютные адреса, они должны быть относительными (к данной странице). Для того, чтобы включить режим cookieless sessions, надо изменить один атрибут в файле Web.config. Режим cookieless session включается установкой значения атрибута cookieless в "true". Например, <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"cookieless="true" timeout="20"/> Как узнать статус Cookies Ранее мы отмечали, что пользователь может выключить механизм Cookies. Но сервер об этом не может узнать, даже если опросит свойство браузера HttpBrowserCapabilities.Cookies. Значение этого свойства сообщает, поддерживается-ли, вообще, механизм Cookies текущей версией браузера. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Чтобы узнать текущий статус Cookies, надо попытаться задействовать механизм, а затем проверить результат. Результат теста следует передать каким-то другим способом, который сработает независимо от установок браузера. Результат проверки следует выработать на основе теста, который состоит в том, что на сервере создаются две страницы: тестирующая (Cookie1) и тестируемая (Cookie2). Произвольная запись cookie, например, пара ("CookieEnabled", "ok") передается из тестирующей страницы в тестируемую. При загрузке тестируемой страницы производится попытка прочесть cookie. Если чтение проходит, значит механизм Cookies работает и результат устанавливается в True, если — нет, то результат проверки устанавливается в False. Покажем, как использовать для этой цели один из рассмотренных ранее механизмов управления состояниями на стороне клиента — QueryString. Это свойство класса HttpRequest, оно тоже дает доступ к коллекции типа NameValueCollection. Эта, сортированная по ключу, коллекция текстовых строк может быть послана клиентом в конце адреса URL и такой способ передачи информации нельзя выключить настройками браузера. Вы помните, что параметры QueryString отделены от адреса URL знаком вопроса. Например, тестируемая страница Cookie2 может передать результат проверки тестирующей странице Cookie1 с помощью такой строки URL: Cookie1.aspx?CookieEnabled=True После адреса тестирующей страницы следует разделитель (?) и пара типа (key, value), в которой посылается положительный ответ на вопрос о поддержке записей Cookies. Суть опыта в том, что управление переходит из тестирующей страницы в тестируемую, а затем возвращается назад вместе с результатом проверки работоспособности cookie. Важно то, что при возврате результата используется другой механизм передачи данных (QueryString). Для иллюстрации этой возможности создайте две новых страницы Cookie1 и Cookie2. В форму страницы Cookie1 вставьте серверную метку с такими свойствами: <asp:Label id="res" runat="server" ForeColor="Red" Font-Bold="True" Font-Names="Arial"/> В ней мы выведем результат тестирования браузера. В метод Page_Load страницы Cookie1 вставьте следующий код. public partial class Misc_Cookie1 : Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //===== Проводился-ли тест ? string s = Request.QueryString["CookieEnabled"];// Результат, возвращаемый в URL if (s == null) { Response.Cookies["CookieEnabled"].Value = "ok"; // Пробная запись Response.Cookies["CookieEnabled"].Expires = DateTime.Now.AddMinutes(1); string thisUrl = Server.UrlEncode(Request.Url.ToString()); //===== Переход к тестируемой странице. //===== Попутно в QueryString посылаем обратный адрес. Response.Redirect("Cookie2.aspx?Redirect=" + thisUrl); } else res.Text = "Browser accepts cookies = " + s; } } } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Если при загрузке страницы в ее URL, кроме адреса, присутствует добавка (запись QueryString), то она содержит результат теста. Этот результат выводится в окно браузера с помощью метки res. Если же коллекция QueryString пуста, то тест еще не проводился. В этом случае (s == null) создается тестовая запись и управление передается странице Cookie2. Одновременно подготавливается возврат в тестирующую страницу (см. код ?Redirect=" + thisUrl). Адрес текущей страницы thisUrl определяется способом, отличным от того, что был использован ранее (вспомните Referer). Здесь он добывается с помощью свойства Request.Url. Адрес нужен тестируемой странице для того, чтобы она знала, куда возвратить результат. Приведем rод разметки тестируемой страницы. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Cookie2.aspx.cs" Inherits="Misc_Cookie2" %> <!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><title>Cookie1 Page</title></head> <body><form id="form1" runat="server"> <div><h2>This is a TestCookie Page</h2> No data - No harm </div></form></body></html> В нашем сценарии эту страницу можно было оставить пустой. Но если кто-то случайно задаст ее адрес, то он не поймет логики пустой страницы. Теперь рассмотрим код класса, поддерживающего тестируемую страницу. public partial class Misc_Cookie2 : Page { protected void Page_Load(object sender, EventArgs e) { //======= Попытка прочесть всю коллекцию Cookies HttpCookie cookie = Request.Cookies["CookieEnabled"]; bool ok = cookie != null; // Результат чтения (проверки) if (ok) // Трюк для уничтожения тестовой записи cookie.Expires = DateTime.Now.AddDays(-1); //==== Возврат результата в тестирующую страницу string back = Request.QueryString["Redirect"]; if (!string.IsNullOrEmpty (back)) Response.Redirect(back + "?CookieEnabled=" + ok.ToString(), true); } } Сначала производится попытка прочесть cookie. Результат запоминается в переменной ok. Если механизм cookie работает, то тестовая запись удаляется. Затем происходит возврат к странице, адрес которой выбирается из QueryString. Адрес возврата конструируется на основе данных, переданных из тестирующей страницы. Он передается методу Response.Redirect, который и осуществляет переход. Запустив приложение, вы должны увидеть результат теста. Он отображен в тексте метки. Рис. 3.8. Иллюстрация способа определения статуса Cookies Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Обратите внимание на адресную строку браузера. В ней виден результат, возвращенный с помощью механизма QueryString. Все хорошо, но надо хорошо продумать сценарий дальнейшей работы. Важно провести проверку один раз, и запомнить ее результат. Такой вариант можно реализовать, призвав на помощь другой механизм хранения данных, например, коллекцию Application. Коллекцию Session использовать нельзя, так как она сама пользуется механизмом cookie. На этом мы закончим тему, посвященную управлению состояниями. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 4. Asynchronous JavaScript 4.1. Механизм Client-Side Callback В ASP.NET 2.0 появился новый механизм обработки событий, который создает для пользователя иллюзию того, что событие обрабатывается на стороне клиента. На самом деле оно обрабатывается на сервере, но обработка происходит асинхронно (в рамках другого потока). Это позволяет исключить полную перестройку страницы. Страница лишь достраивается. В этом механизме, называемом Client-Side Callback, важную роль играет скрипт на стороне клиента (JScript). Именно он и служит посредником между серверным кодом и обновляемым обликом клиентской страницы. Сначала рассмотрим цепь событий, генерируемых при обычном обращении к серверу. Эти события провоцируются любым из серверных элементов, который генерирует HTTP Post request, то есть, отсылает данные на сервер (например, кнопкой Submit). На рис. 4.1. показана цепь событий, генерируемых на сервере при обработке запроса типа Post. Большинство серверных элементов управления реализуют интерфейс IPostbackEventHandler, который заявляет всего один метод RaisePostBackEvent. Page Page after postback Server events Postback Init Load State Process postback data Load Postback events Save state PreRender Render Unload Рис. 4.1. Цепь событий, генерируемых при обычном обращении к серверу Здесь важно понять, что в конце цикла браузер полностью перегружает страницу. Теперь сравним эту цепь с тем, что происходит при асинхронном обращении к серверу (Client-Side Callback). Page Page Callback JScript JScript Async Request Init Load State Server events Process postback data Result Load Callback events Unload Рис. 4.2. Цепь событий, генерируемых при асинхронном обращении к серверу Обратите внимание на то, что сервер не перестраивает страницу (отсутствуют блоки PreRender и Render). Вместо них появились блоки кода, работающего на стороне клиента (JScript). Клиентский скрипт (на схеме справа) аккуратно, без мельканий отображает данные, полученные от сервера в фоновом режиме. Пользователь не замечает момента обращения к серверу, так как страница не обновляется в обычном смысле этого слова. Функция JScript (на схеме слева) генерирует асинхронный запрос, котоый выполняется по схеме, определенной интерфейсом ICallbackEventHandler. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Этот интерфейс заявляет два метода: RaiseCallbackEvent и GetCallbackResult. Первый метод должен предоставить код обработки события на сервере, а второй — возвратить результат в виде строки текста (string result). Функция JScript (на схеме справа), не перерисовывая всю страницу, помещает результирующую строку в один из элементов страницы. Например: box.InnerText = result; или box.InnerHTML = result;. Множество технологий, использующих описанный выше механизм, попали в одну категорию, которая носит имя AJAX (акроним для Asynchronous Javascript and XML). Поэтому создайте новую папку в нашем проекте и назовите ее AJAX. Добавьте в нее новую страницу с именем Callback.aspx и введите такой код разметки. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Callback.aspx.cs" Inherits="AJAX_Callback" %> <!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><title>Callback Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> <script type="text/javascript"> function LookUpStock(id) { var list = document.getElementById(id), index = list.selectedIndex, text = (index != -1 ? list.options[index].text : "-1"); CallServer(text, ""); } function ReceiveServerData(res) { result.innerHTML = res; } </script> </head> <body><form id="form1" runat="server"> <div> <h2>Company Stock Values</h2> Companies (delayed) <br /> <asp:ListBox ID="box" runat="server"/>&nbsp;&nbsp;&nbsp; <br /><br /> <button onclick="LookUpStock('box')">Look Up</button>&nbsp;&nbsp;&nbsp; <span id="result"></span><br /> </div> </form></body></html> Функция ReceiveServerData будет вызвана по окончании асинхронного обращения к серверу. Она выведет результат в выбранный нами элемент управления страницы. В нашем случае результат появится в элементе <span> с id="result". При нажатии на кнопку Look Up вызывается функция JScript LookUpStock, которая добывает текст строки списка box, выбранной пользователем в данный момент. Этот текст передается в функцию JScript CallServer. Необычно то, что этой функции пока нет. Мы должны попросить ASP.NET сгенерировать ее в ходе обработки события Load. Именно она осуществляет асинхронный вызов сервера. Ее имя произвольно, но она должна иметь два аргумента: первый—произвольная информащия, передаваемая на сервер (в нашем случае—это выбранная строка списка, или "–1"), второй — JScript-код, выполняемый в случае ошибки. Итак, надо иметь как минимум три функции JScript. Две вы видите (LookUpStock и ReceiveServerData), а третью (CallServer) надо создать неявно с помощью метода RegisterClientScriptBlock класса ClientScriptManager. Это нужно для повышения степени безопасности внедряемого скрипта. Ранее мы уже пользовались услугами класса ClientScriptManager, и вы, возможно, помните, что его метод RegisterClientScriptBlock требует задать тип объекта, который генерирует JScript. Это и Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 является мерой повышения степени безопасности внедряемого скрипта. Разработчики Microsoft прячут от нас логику асинхронной обработки событий на сервере, и мы не знаем детали работы метода RegisterClientScriptBlock. Теперь рассмотрим код поддержки страницы. public partial class AJAX_Callback : Page, ICallbackEventHandler { string res = null; protected ListDictionary catalog; protected void Page_Load(object sender, EventArgs e) { string script = "function CallServer(arg, context) { " + ClientScript.GetCallbackEventReference( this, "arg", "ReceiveServerData", "context") + "} ;"; Page.ClientScript.RegisterClientScriptBlock (GetType(), "CallServer", script, true); catalog = new ListDictionary(); catalog.Add ("ГазПром", 195); catalog.Add ("ТатНефть", 124); catalog.Add ("СургутНефтеГаз", 98); catalog.Add ("Esso", 1700); box.DataSource = catalog; box.DataTextField = "key"; box.DataBind(); } public void RaiseCallbackEvent(string arg) { res = catalog[arg] != null ? ("Stock value: " + catalog[arg].ToString()) : "Please, select a company"; } public string GetCallbackResult() { return res; } } Обратите внимание на то, что класс AJAX_Callback обязуется реализовать интерфейс ICallbackEventHandler. Это условие необходимо для работы механизма Client-Side Callback. Два последних метода RaiseCallbackEvent и GetCallbackResult, как было указано выше, принадлежат интерфейсу ICallbackEventHandler. Результат обработки события CallbackEvent, вычисляемый в методе RaiseCallbackEvent, зависит от аргумента arg, который приходит от клиента (является частью запроса). В нашем случае аргумент представляет собой строку списка, которую выбрал пользователь, работая с формой Callback.aspx. Главный секрет механизма Client-Side Callback заключен в JScript-функции CallServer, которая создается на стороне сервера (см. вызов RegisterClientScriptBlock), но работает на стороне клиента. Она и осуществляет асинхронный запрос, работающий в фоновом режиме. Для того, чтобы результат обработки события на сервере безшовно внедрился в активную страницу браузера, текст функции CallServer должен содержать ссылку на функцию ReceiveServerData, которая уже существует в блоке клиентского скрипта, но пока не связана с обработкой события. Ссылка на функцию ReceiveServerData добывается с помощью метода GetCallbackEventReference класса ClientScriptManager. Все перечисленные действия выполняют первые два, довольно емких, оператора метода Page_Load. Остальной код метода имитирует обращение к источнику данных по курсам акций нефтяных компаний, Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 расположенному на сервере. Роль источника данных выполняет коллекция catalog типа ListDictionary. Это специализированная коллекция, работающая по принципу словаря. Список <asp:ListBox ID="box" runat="server"/>, расположеннвй на форме связан с коллекцией пар (key, value) механизмом DataBinding. Установка ("key") свойства DataTextField объекта box говорит о том, что список должен содержать первые элементы пар, то есть, имена компаний, а не текущие курсы акций ("value"). Запустите страницу на выполнение и убедитксь в том, что при нажатии на кнопку Look Up происходит обращение к серверу, но браузер при этом не обновляет всю страницу. Рис. 4.3. Кнопка Look Up запускает асинхронный вызов сервера 4.2. Реакция на стороне клиента В настоящий момент выделенная строка списка и строка результата синхронизированы только после нажатия кнопки Look Up. Хочется вовсе убрать эту кнопку и генерировать асинхронный запрос к серверу при каждом изменении выбранной строки списка компаний. Список реализован серверным элементом <asp:ListBox>. Вы помните, что такие элементы не умеют реагировать на скоростные события на стороне клиента. ListBox имеет встроенное событие SelectedIndexChanged, но реакция на него будет отложена до прибытия данных на сервер. Нас же интересует реакция на стороне клиента. Рассмотрим вариант с элементом <select runat="server">. Этот элемент является серверным (так как имееет имеет атрибут runat="server"), но он, в отличие от ListBox, имеет встроенное событие onchange, которое способно обеспечить реакцию на стороне клиента. В то же время, серверный вариант <select> поддерживает механизм DataBinding, который нам необходим. Для того, чтобы сравнить оба варианта реакции на выбор строки в списке создайте в папке AJAX новый ASPX-документ по имени Callback2. Код разметки этого документа приведен ниже. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Callback2.aspx.cs" Inherits="AJAX_Callback2" %> <!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><title>Callback2 Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> <script type="text/javascript"> function LookUpStock(text) { CallServer(text, ""); } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 function SetBox() { var box = document.getElementById("box"), id = box.selectedIndex, t = '-1'; // Пессимистический прогноз if (id != -1) t = box.options[id].text; LookUpStock(t); } function ReceiveServerData(res) { result.innerHTML = res; } </script> </head> <body><form id="form1" runat="server"> <div> <h2>Company Stock Values</h2> Companies (delayed)&nbsp;&nbsp;&nbsp;&nbsp;Companies (instant)<br /> <asp:ListBox ID="box" runat="server" />&nbsp;&nbsp;&nbsp; <select id="sel" runat="server" onchange="LookUpStock(options[selectedIndex].innerText)" onblur="selectedIndex=-1;" onfocus="box.selectedIndex=-1;" size="4" /> <br /><br /> <button onclick="SetBox()">Look Up</button>&nbsp;&nbsp;&nbsp; <span id="result"></span><br /> </div> </form></body></html> На форме (справа от ListBox) расположен серверный элемент <select>. <select id="sel" runat="server" onchange="LookUpStock(options[selectedIndex].text)" onblur="selectedIndex=-1;" onfocus="box.selectedIndex=-1;" size="4" /> Так как <select> относится к категории HTML Server Controls, то он не обременен ограничениями элементов из категории Web Server Controls. В нем присутствуют атрибуты, позволяющие реагировать на скоростные события. Мы используем три таких атрибута: onchange, onblur и onfocus. Событие onblur возникает в момент выхода фокуса из элемента. Так как на форме одновременно существуют два списка, то нам важно показать какой из них работает в данный момент. При выходе фокуса за пределы элемента <select> мы убираем подсветку строки, которая была активна в нем до этого события. Событие onfocus, наоборот, возникает в момент прихода фокуса в элемент <select>. В этот момент надо убрать подсветку строки из ленивого списка box. Сам он не в состоянии это сделать, так как он относится к категории Web Server Controls (ленивых элементов). Событие onchange возникает в момент выбора какой-либо из строк списка. В этот момент мы запускаем механизм асинхронного запроса с помощью Jscript-функции LookUpStock. Обратите внимание на то, что она стала проще, чем в предыдущем варианте. function LookUpStock(text) { CallServer(text, ""); } Функция JScript SetBox перед тем, как вызвать LookUpStock, сражается с возможным вариантом развития событий, когда в списке box строка не выбрана. function SetBox() { var box = document.getElementById("box"), id = box.selectedIndex, t = '-1'; // Пессимистический прогноз Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 if (id != -1) t = box.options[id].text; LookUpStock(t); } Код поддержки страницы Callback2 в основном должен быть таким же как и в предыдущем варианте, но в него следует добавить код, осуществляющий привязку второго списка sel (типа <select>) к данным каталога catalog (объекта класса ListDictionary). Вот этот код. sel.DataSource = catalog; sel.DataTextField = "key"; sel.DataBind(); Запустите приложение и убедитесь, что при использовании второго списка нет нобходимости пользоваться кнопкой Look Up. В следующем разделе мы придумает ей другое назначение. Рис. 4.4. Выбор в списке запускает асинхронный вызов сервера Вопрос Вспомните уже пройденный вами материал и ответьте на вопрос: "Можно ли добиться поведения, сходного с тем, что проявляет список <select>, от сугубо серверного элемента <asp:ListBox>?" 4.3. Генерация таблицы Приведем возможный ход рассуждений при формулировании ответа на поставленный вопрос. Все серверные элементы (Web Server Controls) в конце концов будут преобразованы машиной ASP.NET (ASP.NET engine) в обычные элементы HTML. Элемент <asp:ListBox> в клиентском коде HTML будет заменен своим аналогом, элементом <select>. Вспомним, что ASP.NET дает возможность управлять процессом трансформации серверного элемента в обычный. Мы пользовались этой возможностью ранее. Нам надо добавить атрибут onchange в аналог серверного элемента и в качестве его значения задать текст, который вызывает функцию JScript. Обычно это делается в методе Page_Load с помощью кода на C#. Создайте в папке AJAX еще один документ ASPX по имени Callback3. Код разметки этого документа почти ничем не сильно отличается от кода разметки документа Callback, поэтому скопируйте его оттуда, но будьте внимательны с директивой Page, она должна содержать правильные ссылки. Затем внесите изменения, как показано ниже. Во избежание ошибок, приведем почти весь код aspx-файла. <head><title>Callback3 Page</title> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <script type="text/javascript"> function LookUpStock(text) { CallServer(text, ""); } function ReceiveServerData(res) { result.innerHTML = res; } </script> </head> <body><form id="form1" runat="server"> <div> <h2>Company Stock Values</h2> Companies (instant) <br /> <asp:ListBox ID="box" runat="server"/>&nbsp;&nbsp;&nbsp; <br /><br /> <button onclick="LookUpStock()">Look Up</button>&nbsp;&nbsp;&nbsp; <span id="result"></span><br /> </div> </form></body></html> Обратите внимание на изменения в сигнатуре функции LookUpStock по сравнению вариантом 1. Здесь мы пользуемся тем, что JScript позволяет вызывать любую функцию с произвольным количеством параметров. При вызове LookUpStock без параметров (чуть позже) будет создана таблица, при вызове с текстовым параметром, будет выведен текст. Код поддержки страницы Callback3 также скопируйте из документа Callback, и также не забудьте про различие имен. Имя класса должно быть другим (AJAX_Callback3). В метод Page_Load вставьте код, который указывает ASP.NET на изменение атрибутов списка box. box.Attributes.Add("onchange", "LookUpStock(options[selectedIndex].text)"); Запустите приложение и убедитесь, что страница работает так, как и ранее. Проверьте реакцию на нажатие кнопки Look Up. Теперь очевидно, что функциональность кнопки оправдана только в начальный момент, когда пользователь еще не выбрал строку в списке компаний. Внесите в метод RaiseCallbackEvent изменения, которые придадут кнопке Look Up другую функциональность. Не забудьте добавить директиву using System.Collections.Specialized; public void RaiseCallbackEvent(string arg) { if (catalog[arg] != null) res = "Stock value: " + catalog[arg].ToString(); else { res = "<br/><br/><table><tr><th>Company</th><th>Stock Value</th></tr>"; foreach (DictionaryEntry de in catalog) res += "<tr><td>" + de.Key + "</td><td>" + de.Value.ToString() + "</td></tr>"; res += "</table>"; } } Запустите страницу и убедитесь, что логика обработки асинхронного запроса стала более осмысленной. Теперь мы можем получить от сервера как одно, конкретное значение курса, так и суммарную сводку по курсам акций всех компаний. Ответьте на вопрос: "Как бы вы изменили алгоритм обработки запроса, если бы источник данных содержал тысячи строк? " Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Рис. 4.5. Асинхронный запрос генерирует HTML-код таблицы 4.4. Асинхронный запрос к таблице базы данных Следующий пример использует тот же механизм обработки событий (Client-Side Callback), но работа ведется с двумя таблицами учебной базы данных Pubs (Публикации). Я предполагаю, что вместе со студией вы установили упрощенную (и бесплатную) версию SQL Server (SQLExpress) и установили учебные базы, поставляемые вместе со студией. Если базы отсутствуют, то можно взять их на сайте Microsoft. Другой вариант — создать свою собственную базу данных в рамках SQLExpress. В этом случае придется изменить код и подстроить его под схему ваших таблиц. В студии 2005 базы данных достаточно просто установить с помощью команды: Start→Programs→ Microsoft .NET Framework SDK→QuickStart Tutorials. Вас встретит Web-приложение, которое поможет решить проблему установки и конфигурирования учебных баз. Идея страницы такова. Пользователь видит содержимое таблицы publishers (издательства) базы Pubs. Таблица имеет такие колонки: идентификатор (первичный ключ), страна и город конкретного издательства. Строкам таблицы соответствует множество издательств. При выборе строки таблицы publishers JScript генерирует асинхронный запрос к другой таблице titles, которая содержит характеристику книг, выпущенных данным издательством. Результат запроса к таблице titles мы должны получить в ходе обработки события на сервере. Результат представляет собой текстовую строку, которая возвращается (незаметно для пользователя) в клиентский браузер и воспроизводится в виде таблицы <table> с помощью функции JScript с именем ReceiveServerData, как и было в предыдущих примерах. Особенность этого примера состоит в том, что первая таблица представлена сугубо серверным элементом <asp:GridView>, который не имеет встроенных событий: onclick и onkeydown. Но нам необходимо обеспечить реакцию на выбор строки таблицы на стороне клиента, иначе, как вы теперь знаете, механизм Client-Side Callback не будет работать. Поэтому нам придется внедрить в отображение элемента <asp:GridView>, которым является <table>, реакции на эти (скоростные) события. Добавьте в папку App_Data копию файла pubs.mdf, который вы либо нашли по адресу: Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 C:\Program Files\Microsoft SQL Server\MSSQL\Data либо взяли на сайте Microsoft. Откройте файл Web.config (XML-документ вашего проекта), Отыщите в нем XML-элемент <connectionStrings>, который вложен в элемент <configuration>, Вставьте внутрь элемента <connectionStrings>, XML-элемент <add>, приведенный ниже. Он определяет идентификатор строки соединения с базой данных Pubs. <add name="pubsConnectionString" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\pubs.mdf; Integrated Security=True;Connect Timeout=30;User Instance=True" providerName="System.Data.SqlClient"/> Вы можете, но это не обязательно, добавить новое соединение в список строк соединений инструмента Server Explorer. Воспользуйтесь для этой цели диалогом Add Connection, который открывается командой контекстного меню, вызванного над строкой Data Connections. Добавьте в папку AJAX новую страницу с именем CallbackDB.aspx и введите такой код разметки. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CallbackDB.aspx.cs" Inherits="AJAX_CallbackDB" %> <!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><title>CallbackDB Page</title> <script type="text/javascript"> var cur = null, // Текущая строка таблицы firstRow = null, // Первая строка таблицы hiColor = "#bec5ff"; // Цвет подсветки function Init() { firstRow = grid.getElementsByTagName("tr")[1]; document.body.attachEvent ("onkeydown", OnKeyDown); // Привязка к событию SelectRow (firstRow); } function OnKeyDown(e) { if (e.keyCode == 39 || e.keyCode == 40) MoveDown(); else if (e.keyCode == 37 || e.keyCode == 38) MoveUp(); } function MoveDown() { var next = cur.nextSibling; if (next == null) next = firstRow; SelectRow(next); } function MoveUp() { var prev = cur.previousSibling; if (prev == firstRow.previousSibling) prev = grid.tBodies[0].lastChild; SelectRow(prev); } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 function SelectRow(row) { SetBkColor(cur, ""); SetBkColor(row, hiColor); ProcessRow (row); } function SetBkColor(row, clr) { if (row != null && row.children != null) for (var i=0; i<row.children.length; i++) row.children[i].runtimeStyle.backgroundColor = clr; } function ProcessRow(row) { cur = row; CallServer(cur.cells[0].innerText, ""); } function ReceiveServerData(res) { result.innerHTML = res; } </script> <link href="../StyleSheet.css" rel="stylesheet" type="text/css" /> </head> <body onload="Init()"><form id="form1" runat="server"> <div> <asp:Label ID="lblHeader" runat="server" Font-Bold="True" Font-Names="Verdana" Font-Size="Larger" ForeColor="MidnightBlue" Text="Asynchronous Detail" /><br /><br /> <asp:GridView ID="grid" runat="server" CellPadding="5" DataSourceID="pubsDataSource" DataKeyNames="pub_id" AutoGenerateColumns="False" > <HeaderStyle BackColor="Black" ForeColor="White" Font-Names="Tahoma" Font-Size="Small" /> <RowStyle Font-Names="Tahoma" Font-Size="Small" ForeColor="Black" Wrap="True" /> <AlternatingRowStyle BackColor="Wheat" BorderColor="Black" BorderStyle="Solid" BorderWidth="1px" /> <Columns> <asp:BoundField DataField="pub_id" HeaderText="ID" ReadOnly="true" SortExpression="pub_id" /> <asp:BoundField DataField="country" HeaderText="Country" /> <asp:BoundField DataField="city" HeaderText="City" SortExpression="city" /> </Columns> </asp:GridView> <asp:SqlDataSource ID="pubsDataSource" runat="server" SelectCommand="SELECT * FROM [publishers]" ConnectionString="<%$ ConnectionStrings:pubsConnectionString %>"> </asp:SqlDataSource><br /> <span id="result"></span><br /> </div> </form></body></html> Клиентский скрипт, кроме необходимых функций: ReceiveServerData и ProcessRow, содержит ряд функций, которые реализуют навигацию и выделение строк таблицы на стороне клиента. Напомню, что <asp:GridView> такими свойствами не обладает, он все привык делать на сервере. Функция ProcessRow провоцирует асинхронный запрос к серверу (см. вызов функции CallServer, производимый внутри нее). Функцию CallServer, как и ранее, предстоит создать динамически, при загрузке страницы на сервере. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Анализ кода JScript требует заметных усилий, определенных знаний и умений, но они относятся скорее к общей технике программирования на С-подобных языках, чем технологии ASP.NET. Некоторые сведения из области программирования на JScript вы можете получить из Приложения. Теперь рассмотрим код поддержки страницы, который кроме стандартного обращения к методу RegisterClientScriptBlock содержит обращение к базе данных и генерацию результата в виде текстовой строки. using using using using using using using System; System.Collections; System.Data; System.Data.SqlClient; System.Web; System.Web.UI; System.Web.UI.WebControls; public partial class AJAX_CallbackDB : Page, ICallbackEventHandler { private string res = null; protected void Page_Load(object sender, EventArgs e) { foreach (GridViewRow row in grid.Rows) row.Attributes.Add("onclick", "SelectRow(this);"); string script = "function CallServer(arg, context) { " + ClientScript.GetCallbackEventReference( this, "arg", "ReceiveServerData", "context") + "}"; ClientScript.RegisterClientScriptBlock(GetType(), "CallServer", script, true); } public string GetCallbackResult() { return res; } public void RaiseCallbackEvent(string arg) { SqlConnection cn = new SqlConnection( @"Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\pubs.mdf; Integrated Security=True;Connect Timeout=30;User Instance=True"); SqlCommand cmd = new SqlCommand("Select * From [titles] WHERE pub_id='"+arg+"'", cn); cn.Open(); SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); if (!reader.HasRows) res = "No Information"; else { res = "<table style='font-size:80%;'><tr><th>ID</th><th>Title</th>" + "<th>Notes</th><th>Type</th><th>Price</th></tr>"; while (reader.Read()) { res += "<tr><td>" + arg + "</td><td>" + reader["Title"].ToString() + "</td><td>" + reader["Notes"].ToString() + "</td><td>" + reader["Type"].ToString() + "</td><td>" + reader["Price"].ToString() + "</td></tr>"; } res += "</table>"; } } Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 } Важной особенностью нашего примера я считаю тот факт, что реакции на события onclick и onkeydown вводятся с помощью разных технологических приемов, которые вам следует взять на вооружение. Реакция на событие onclick генерируется в ходе выполнения серверного кода таким образом: foreach (GridViewRow row in grid.Rows) row.Attributes.Add("onclick", "SelectRow(this);"); Реакция на событие onkeydown генерируется на стороне клиента в ходе обработки события body.onload: document.body.attachEvent ("onkeydown", OnKeyDown); Рис. 4.6. Асинхронный запрос к таблице базы данных Способ обращения к таблице titles, которая связана с таблицей publishers, относится к технологии ADO.NET, которая тесно связана с ASP.NET. Проследим связи. Запрос книг фильтруется строкой arg, которая асинхронно получена от сервера и представляет собой ID издательства (см. код ниже). new SqlCommand("Select * From [titles] WHERE pub_id='" + arg + "'", cn); Идентификатор ID, в свою очередь, соответствует выбранной в данный момент строке GridView (ID="grid"), последняя привязана к данным источника pubsDataSource типа <asp:SqlDataSource>. <asp:GridView ID="grid" runat="server" CellPadding="5" DataSourceID="pubsDataSource" DataKeyNames="pub_id" AutoGenerateColumns="False" > Источник данных pubsDataSource настроен на работу с таблицей publishers, что с помощью декларативного синтаксиса определено в коде разметке нашей страницы. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 <asp:SqlDataSource ID="pubsDataSource" runat="server" SelectCommand="SELECT * FROM [publishers]" ConnectionString="<%$ ConnectionStrings:pubsConnectionString %>"> Запустите приложение и проверьте динамику обработки асихронных запросов к серверу. Вы должны увидеть нечто похожее на рис. 4.6. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 Список литературных источников 1. Мэтью Мак-Дональд, Марио Шпушта. Microsoft ASP.NET 3.5 с примерами на C# 2008 для профессионалов. — Издательство: Вильямс, 2008 г., 1424 стр. 2. Роб Камерон, Дэйл Михалк. ASP.NET 3.5, компоненты AJAX и серверные элементы управления для профессионалов. — Издательство: Вильямс, 2008 г., 608 стр. 3. Робин Парс, Лоренс Морони, Джон Гриб. Основы ASP.NET AJAX. Издательство: Вильямс, 2008 г., 288 стр. 4. Дино Эспозито. Microsoft ASP.NET 2.0. Базовый курс. — Издательства: Питер, Русская Редакция, 2007 г., 688 стр. 5. Марко Беллиньясо. Разработка Web-приложений в среде ASP.NET 2.0. Задача-проектрешение. Издательство: Вильямс, 2007 г., 640 стр. 6. Эндрю Троелсен. Язык программирования C# 2005 и платформа .NET 2.0. 3-е издание. — Издательский дом "Вильямс", СПб, 2007, 1155 с. 7. Джеффри Рихтер. Программирование на платформе Microsoft .NET Framework 2.0 на языке C#. — Издательство: Русская редакция, ПИТЕР, 2007, 658 с. 8. И. Квинт. HTML и CSS на 100%. — Издательство: Питер, 2008 г., 352 стр. 9. Артемий Ломов. HTML, CSS, скрипты: практика создания сайтов. — Издательство: БХВПетербург, 2007 г., 416 стр. 10. Э. Кастро. HTML и CSS для создания Web-страниц. — Издательство: НТ Пресс, 2006 г., 126 с. 11. Андерсон Р., Фрэнсис Б., Хомер А. и др. ASP.NET для профессионалов. В 2 томах. — Издательство: Лори, 2004 г., 1164 стр. 12. Дино Эспозито.Знакомство с технологией Microsoft ASP.NET 2.0 AJAX. — Издательства: Русская Редакция, Питер, 2007 г., 320 стр. 13. Дино Эспозито.Microsoft ASP.NET 2.0. Углубленное изучение. — Издательства: Питер, Русская Редакция, 2007 г., 592 стр. 14. Игорь Гробов. Разработка Web-портала в ASP.NET 2.0 и SharePoint 2007. — Издательство: БХВ-Петербург, 2008 г., 656 стр. 15. Karli Watson. Beginning C# 2005 Databases. — Wiley Publishing, Inc. 2006., pp.529. 16. Чарльз Петцольд. Программирование для Microsoft WINDOWS на С#. 2 тома — Издательство: Русская редакция, 576 с. и 624 с. 17. Гуннерсон Э. Введение в С#. Библиотека программиста. — Издательство: Питер, 2001., 304 с. 18. Симон Робинсон, Олли Корнес и др. C# для профессионалов, 2 тома. — Издательство "ЛОРИ", 2003, 516с. и 548с. 19. Джесс Либерти. Программирование на C#. — Издательство: O"REILLY, 2003, 341c. 20. Stephen Walther. ASP.NET 3.5 Unleashed. — Wiley Publishing, Inc., 2007, 548 pp. 21. Chris Ullman, Lucinda Dykes. Beginning Ajax. — Wiley Publishing, Inc., 2007, 530 pp. 22. Cristian Darie, Zak Ruvalcaba. Build Your Own ASP.NET 2.0 Web Site Using C# & VB. — SitePoint Pty. Ltd., 2006, 715 pp. 23. Andy Budd. CSS Mastery: Advanced Web Standards Solutions Advanced Web Standards Solutions, — Apress, 2006, 241 pp. 24. Paul Wilton, John W. Colby. Beginning SQL. — Wiley Publishing, Inc. 2005, 522 pp. 25. Bill Hamilton, Matthew MacDonald. ADO.NET in a Nutshell. — O'Reilly, 2004, 620 pp. 26. MSDN http://msdn2.microsoft.com Книги, доступные в сети Internet 27. Russell Jones Mastering.ASP.NET. 28. Купцевич Альманах Web. Модуль 2. Разработка Web-приложений с помощью ASP.NET. Черносвитов А.В. (Alexcher@Avalon.ru) © 2007 29. Основы ASP.NET 2.0 (Intuit.ru) 30. Марк Кэмпбел Строим Web-сайты.